buildKey('SR12345', 'AUTH_TOKEN_001', 'TX_TID_001'); $b = $this->buildKey('SR12345', 'AUTH_TOKEN_001', 'TX_TID_001'); $this->assertSame($a, $b); } /** * @scenario context=authorize_card, threat=replay_token_short, callback_state=second_arrival_within_60s * @effects auth_token_within_60s_blocked */ public function test_key_differs_when_token_differs(): void { $a = $this->buildKey('SR12345', 'AUTH_TOKEN_001', 'TX_TID_001'); $b = $this->buildKey('SR12345', 'AUTH_TOKEN_002', 'TX_TID_001'); $this->assertNotSame($a, $b); } /** * @scenario context=authorize_card, threat=replay_token_short, callback_state=second_arrival_within_60s * @effects auth_token_within_60s_blocked */ public function test_key_differs_when_tx_tid_differs(): void { $a = $this->buildKey('SR12345', 'AUTH_TOKEN_001', 'TX_TID_001'); $b = $this->buildKey('SR12345', 'AUTH_TOKEN_001', 'TX_TID_002'); $this->assertNotSame($a, $b); } /** * 다른 가맹점 (MID) 라면 같은 token+tid 도 충돌 없음. * 멀티 가맹점 환경에서 한 가맹점의 dedupe 가 다른 가맹점에 영향 안 미침. * * @scenario context=authorize_card, threat=replay_token_short, callback_state=first_arrival * @effects auth_token_cache_key_uses_sha256_of_mid_token_txtid */ public function test_key_differs_when_mid_differs(): void { $a = $this->buildKey('SR12345', 'AUTH_TOKEN_001', 'TX_TID_001'); $b = $this->buildKey('SR99999', 'AUTH_TOKEN_001', 'TX_TID_001'); $this->assertNotSame($a, $b); } /** * @scenario context=naverpay_callback, threat=replay_token_short, callback_state=first_arrival * @effects auth_token_cache_key_uses_sha256_of_mid_token_txtid */ public function test_key_has_expected_prefix(): void { $key = $this->buildKey('SR12345', 'AT001', 'TX001'); $this->assertStringStartsWith('nicepay_auth_token_seen:', $key); } /** * sha256 hex = 64자. * @scenario context=authorize_card, threat=replay_token_short, callback_state=first_arrival * @effects auth_token_cache_key_uses_sha256_of_mid_token_txtid */ public function test_key_has_stable_length(): void { $key = $this->buildKey('SR12345', 'AT001', 'TX001'); // 'nicepay_auth_token_seen:' (24) + sha256 hex (64) = 88 $this->assertSame(88, strlen($key)); } /** * 셸 메타문자/제어문자가 mid/token/tid 에 들어와도 hash 함수가 * 결정적 hex 만 산출하므로 cache key 부분에서 인젝션 위험 없음. * * @scenario context=authorize_card, threat=replay_token_short, callback_state=first_arrival * @effects auth_token_cache_key_uses_sha256_of_mid_token_txtid */ public function test_key_safe_with_metachars_in_input(): void { $key = $this->buildKey('SR" || OR 1=1 --', "TOKEN\nLF", "TID;rm"); $this->assertMatchesRegularExpression('/^nicepay_auth_token_seen:[0-9a-f]{64}$/', $key); } /** * @scenario context=kakaopay_callback, threat=replay_token_short, callback_state=second_arrival_outside_60s * @effects auth_token_outside_60s_proceeds */ public function test_key_collision_with_concatenation_blurring(): void { // 만약 구분자 ':' 없이 단순 concat 했다면 ('ab', 'cd', 'ef') 와 ('abc', 'd', 'ef') 가 // 같은 hash 를 만들 수 있다. ':' 구분자 사용으로 이 모호성을 회피하는지 검증. $a = $this->buildKey('SR', '12345', 'TID'); $b = $this->buildKey('SR12345', '', 'TID'); // 구분자 ':' 가 있으면 'SR:12345:TID' vs 'SR12345::TID' → 다른 hash $this->assertNotSame($a, $b); } }