|
@@ -106,6 +106,67 @@ final class SecretScrubbingProcessorTest extends TestCase
|
|
|
self::assertSame('user search hit', $out->message);
|
|
self::assertSame('user search hit', $out->message);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ public function testRawJwtInValueIsScrubbed(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // SEC_REVIEW F65: a JWT logged under any key (not in the
|
|
|
|
|
+ // sensitive-key list) must still be scrubbed by the value
|
|
|
|
|
+ // pattern. JWTs always start `eyJ` (base64url of `{"`), so the
|
|
|
|
|
+ // pattern is anchored on that.
|
|
|
|
|
+ $processor = new SecretScrubbingProcessor();
|
|
|
|
|
+ $jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTYifQ.qj8u_Mt1ZyT5PfksI91X4-3aBwQ-_Pwm';
|
|
|
|
|
+ $record = $this->record('upstream returned id_token', [
|
|
|
|
|
+ 'jwt' => $jwt,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $out = $processor($record);
|
|
|
|
|
+
|
|
|
|
|
+ self::assertSame('eyJ***', $out->context['jwt']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function testRawJwtInMessageIsScrubbed(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $processor = new SecretScrubbingProcessor();
|
|
|
|
|
+ $jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI3In0.q-MT_OmW-fakesigvaluehere';
|
|
|
|
|
+ $record = $this->record("decoded payload from {$jwt} ok", []);
|
|
|
|
|
+
|
|
|
|
|
+ $out = $processor($record);
|
|
|
|
|
+
|
|
|
|
|
+ self::assertStringContainsString('eyJ***', $out->message);
|
|
|
|
|
+ self::assertStringNotContainsString($jwt, $out->message);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function testShortBearerTokenIsScrubbed(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // SEC_REVIEW F65: the previous floor of {20,} let a short
|
|
|
|
|
+ // Bearer slip through. The new floor is {8,}.
|
|
|
|
|
+ $processor = new SecretScrubbingProcessor();
|
|
|
|
|
+ $record = $this->record('outbound', [
|
|
|
|
|
+ 'auth' => 'Bearer abc12345',
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $out = $processor($record);
|
|
|
|
|
+
|
|
|
|
|
+ // 'auth' isn't in the key-name list, so we exercise the value
|
|
|
|
|
+ // pattern. The Bearer prefix stays as a triage breadcrumb.
|
|
|
|
|
+ self::assertSame('Bearer ***', $out->context['auth']);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function testIpAddressDoesNotMatchJwtRegex(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ // False-positive guard: dotted-quad IP looks like
|
|
|
|
|
+ // `int.int.int` but doesn't start with `eyJ`, so it's
|
|
|
|
|
+ // outside the JWT pattern.
|
|
|
|
|
+ $processor = new SecretScrubbingProcessor();
|
|
|
|
|
+ $record = $this->record('handling 192.168.1.1 request', [
|
|
|
|
|
+ 'ip' => '203.0.113.42',
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $out = $processor($record);
|
|
|
|
|
+
|
|
|
|
|
+ self::assertSame('203.0.113.42', $out->context['ip']);
|
|
|
|
|
+ self::assertStringContainsString('192.168.1.1', $out->message);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public function testNestedContextIsScrubbedRecursively(): void
|
|
public function testNestedContextIsScrubbedRecursively(): void
|
|
|
{
|
|
{
|
|
|
$processor = new SecretScrubbingProcessor();
|
|
$processor = new SecretScrubbingProcessor();
|