Fix RedactingFormatter missing secrets nested in array-valued sensitive keys

RedactingFormatter::collect() only added a sensitive key's value to the
secret list when it was a string or Stringable. When the value was an
array, addSecret() silently dropped it, so none of its leaf strings were
collected. PsrLogMessageProcessor json-encodes array context values when
interpolating them into the message, so a secret nested that way could
already be sitting in the message in plain text by the time this formatter
ran, and the output sweep had nothing to remove it with.

Add collectSensitiveValue() to walk a sensitive key's value and collect
every string leaf, however deeply nested, and use it instead of calling
addSecret() directly.
This commit is contained in:
dualfroz committed 2026-09-04 08:38:50 +02:00
1 parent fb5cc5f9ee
commit 7f7dc9b07c
3 files changed
+127 -2

No files matched your search

+28 -2
View File
@@ -203,8 +203,10 @@ final class RedactingFormatter implements WrappingFormatterInterface
if (\is_array($data)) {
foreach ($data as $key => $value) {
if (\is_string($key) && \in_array(strtolower($key), $this->sensitiveKeys, true)) {
// no need to recurse, the whole value is masked in the record anyway
$this->addSecret($value, $secrets);
// the value itself is masked in the record as a whole, but a nested
// array's leaves can still leak elsewhere (e.g. json-encoded into the
// message by PsrLogMessageProcessor), so collect every leaf too
$this->collectSensitiveValue($value, $secrets, $depth + 1);
continue;
}
@@ -239,6 +241,30 @@ final class RedactingFormatter implements WrappingFormatterInterface
}
}
/**
* Collects every string leaf found inside a value already known to be sensitive
* (the value of a key matching one of the configured sensitive keys), regardless
* of how deeply it is nested in arrays.
*
* @param list<string> $secrets
*/
private function collectSensitiveValue(mixed $value, array &$secrets, int $depth): void
{
if ($depth > self::MAX_DEPTH) {
return;
}
if (\is_array($value)) {
foreach ($value as $item) {
$this->collectSensitiveValue($item, $secrets, $depth + 1);
}
return;
}
$this->addSecret($value, $secrets);
}
/**
* Reflects the properties of an object matching a #[SensitiveParameter] constructor parameter
*