@@ -528,7 +528,7 @@ public function testLogWithPasswordRedactionOnShortValue(): void
528528 /** @Then the password should still be fully masked */
529529 $ output = $ this ->streamContents ();
530530
531- self ::assertStringContainsString ('** ' , $ output );
531+ self ::assertStringContainsString ('******** ' , $ output );
532532 self ::assertStringNotContainsString ('"password":"ab" ' , $ output );
533533 }
534534
@@ -556,6 +556,46 @@ public function testLogWithPasswordRedactionOnNestedField(): void
556556 self ::assertStringContainsString ('"username":"admin" ' , $ output );
557557 }
558558
559+
560+ public function testLogWithPasswordRedactionDoesNotRevealValueLength (): void
561+ {
562+ /** @Given a structured logger with password redaction */
563+ $ logger = StructuredLogger::create ()
564+ ->withStream (stream: $ this ->stream )
565+ ->withComponent (component: 'auth-service ' )
566+ ->withRedactions (PasswordRedaction::default ())
567+ ->build ();
568+
569+ /** @When logging passwords of different lengths */
570+ $ logger ->info (message: 'login.short ' , context: ['password ' => '123 ' ]);
571+ $ logger ->info (message: 'login.long ' , context: ['password ' => 'mySuperLongP@ssw0rd!123 ' ]);
572+
573+ /** @Then both should produce the same fixed-length mask */
574+ $ lines = array_filter (explode ("\n" , $ this ->streamContents ()));
575+
576+ self ::assertStringContainsString ('"password":"********" ' , $ lines [0 ]);
577+ self ::assertStringContainsString ('"password":"********" ' , $ lines [1 ]);
578+ }
579+
580+ public function testLogWithPasswordRedactionWithCustomFixedMaskLength (): void
581+ {
582+ /** @Given a structured logger with password redaction configured with a custom fixed mask length */
583+ $ logger = StructuredLogger::create ()
584+ ->withStream (stream: $ this ->stream )
585+ ->withComponent (component: 'auth-service ' )
586+ ->withRedactions (PasswordRedaction::from (fields: ['password ' ], fixedMaskLength: 12 ))
587+ ->build ();
588+
589+ /** @When logging with a password field */
590+ $ logger ->info (message: 'login.attempt ' , context: ['password ' => 'abc ' ]);
591+
592+ /** @Then the mask should have exactly 12 asterisks */
593+ $ output = $ this ->streamContents ();
594+
595+ self ::assertStringContainsString ('"password":"************" ' , $ output );
596+ self ::assertStringNotContainsString ('abc ' , $ output );
597+ }
598+
559599 public function testLogWithNameRedaction (): void
560600 {
561601 /** @Given a structured logger with name redaction */
0 commit comments