1
0

SecretScrubbingProcessorTest.php 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Unit\Logging;
  4. use App\Logging\SecretScrubbingProcessor;
  5. use Monolog\Formatter\JsonFormatter;
  6. use Monolog\Level;
  7. use Monolog\LogRecord;
  8. use PHPUnit\Framework\TestCase;
  9. final class SecretScrubbingProcessorTest extends TestCase
  10. {
  11. public function testBearerTokenInContextIsScrubbed(): void
  12. {
  13. $processor = new SecretScrubbingProcessor();
  14. $record = $this->record('outbound api call', [
  15. 'authorization' => 'Bearer irdb_svc_ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
  16. ]);
  17. $out = $processor($record);
  18. self::assertSame('***', $out->context['authorization']);
  19. }
  20. public function testFormattedOutputDoesNotLeakBearerToken(): void
  21. {
  22. $processor = new SecretScrubbingProcessor();
  23. $record = $this->record('outbound', [
  24. 'headers' => ['Authorization' => 'Bearer irdb_svc_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
  25. ]);
  26. $out = $processor($record);
  27. $line = (new JsonFormatter())->format($out);
  28. self::assertStringNotContainsString('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', $line);
  29. self::assertStringContainsString('***', $line);
  30. }
  31. public function testLocalAdminPasswordHashKeyScrubbed(): void
  32. {
  33. $processor = new SecretScrubbingProcessor();
  34. $record = $this->record('config', [
  35. 'LOCAL_ADMIN_PASSWORD_HASH' => '$argon2id$v=19$abc$def',
  36. 'OIDC_CLIENT_SECRET' => 'oidc-secret',
  37. ]);
  38. $out = $processor($record);
  39. self::assertSame('***', $out->context['LOCAL_ADMIN_PASSWORD_HASH']);
  40. self::assertSame('***', $out->context['OIDC_CLIENT_SECRET']);
  41. }
  42. public function testNonSensitiveLeftAlone(): void
  43. {
  44. $processor = new SecretScrubbingProcessor();
  45. $record = $this->record('search ok', ['count' => 42, 'q' => '203.0.113.42']);
  46. $out = $processor($record);
  47. self::assertSame(42, $out->context['count']);
  48. self::assertSame('203.0.113.42', $out->context['q']);
  49. }
  50. public function testRawJwtInValueIsScrubbed(): void
  51. {
  52. // SEC_REVIEW F65: JWTs anchored on `eyJ` (base64url of `{"`)
  53. // are scrubbed regardless of the key they're logged under.
  54. $processor = new SecretScrubbingProcessor();
  55. $jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI3In0.qj8u_Mt1ZyT5PfksI91X4Aaa';
  56. $record = $this->record('oidc id_token captured', ['jwt' => $jwt]);
  57. $out = $processor($record);
  58. self::assertSame('eyJ***', $out->context['jwt']);
  59. }
  60. public function testShortBearerIsScrubbed(): void
  61. {
  62. // SEC_REVIEW F65: Bearer floor lowered from {20,} to {8,}.
  63. $processor = new SecretScrubbingProcessor();
  64. $record = $this->record('outbound', ['auth' => 'Bearer abcd1234']);
  65. $out = $processor($record);
  66. self::assertSame('Bearer ***', $out->context['auth']);
  67. }
  68. /**
  69. * @param array<string, mixed> $context
  70. */
  71. private function record(string $message, array $context): LogRecord
  72. {
  73. return new LogRecord(
  74. datetime: new \DateTimeImmutable(),
  75. channel: 'test',
  76. level: Level::Info,
  77. message: $message,
  78. context: $context,
  79. );
  80. }
  81. }