Skip to content

Conversation

@rishi111-code
Copy link

…essage/context/extra)

Introduce a new RedactSensitiveProcessor for Monolog that automatically masks sensitive keys (like password, token, api_key) and user-defined patterns (e.g., bearer tokens, email addresses) in both log messages and record data (context and extra). Full unit tests and documentation included.

Motivation:
Logs frequently leak sensitive information (e.g., auth tokens or credentials). This processor provides a built-in, secure, zero-dependency way to redact common data, meeting a real community need and improving the developer experience ‌— no custom wiring needed. It complements existing processors like UidProcessor by addressing security concerns directly. Better Stack
getparthenon.com

Implementation Details:

New class RedactSensitiveProcessor (in src/Monolog/Processor) implements ProcessorInterface.

Configurable constructor accepts:

$sensitiveKeys (case-insensitive array of keys to mask)

$patterns (regex patterns for message redaction)

$mask (custom replacement string)

It safely ignores invalid regex patterns.

Applies redaction on both context, extra, and the message.

Fully covered by PHPUnit tests including edge cases (nested arrays, invalid regex) in tests/Monolog/Processor/RedactSensitiveProcessorTest.php. Benefits:

Addresses a frequent security requirement directly in the core library.

Safe default behavior; invalid patterns are ignored, preventing runtime errors.

Lightweight and zero-dependency, posing a minimal maintenance burden.

Fully documented and tested, ready for production use.

Backward Compatibility & Performance:
Non-breaking addition. High-performance — simple string and array operations. Regex evaluation is optional and safe. Also doesn’t alter existing logging behavior when not configured.

…essage/context/extra)

Introduce a new RedactSensitiveProcessor for Monolog that automatically masks sensitive keys (like password, token, api_key) and user-defined patterns (e.g., bearer tokens, email addresses) in both log messages and record data (context and extra). Full unit tests and documentation included.

Motivation:
Logs frequently leak sensitive information (e.g., auth tokens or credentials). This processor provides a built-in, secure, zero-dependency way to redact common data, meeting a real community need and improving the developer experience ‌— no custom wiring needed. It complements existing processors like UidProcessor by addressing security concerns directly.
Better Stack
getparthenon.com

Implementation Details:

New class RedactSensitiveProcessor (in src/Monolog/Processor) implements ProcessorInterface.

Configurable constructor accepts:

$sensitiveKeys (case-insensitive array of keys to mask)

$patterns (regex patterns for message redaction)

$mask (custom replacement string)

It safely ignores invalid regex patterns.

Applies redaction on both context, extra, and the message.

Fully covered by PHPUnit tests including edge cases (nested arrays, invalid regex) in tests/Monolog/Processor/RedactSensitiveProcessorTest.php.
Benefits:

Addresses a frequent security requirement directly in the core library.

Safe default behavior; invalid patterns are ignored, preventing runtime errors.

Lightweight and zero-dependency, posing a minimal maintenance burden.

Fully documented and tested, ready for production use.

Backward Compatibility & Performance:
Non-breaking addition. High-performance — simple string and array operations. Regex evaluation is optional and safe. Also doesn’t alter existing logging behavior when not configured.
$message = $record->message;
foreach ($this->patterns as $pattern) {
if (@preg_match($pattern, '') === false) {
continue; // ignore invalid pattern instead of throwing inside logging path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we want to silently ignore failures. If the pattern is invalid and ignored silently, nothing will tell the dev that his expected masking won't heppe.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out — you're absolutely right. Silently ignoring an invalid regex could lead to unexpected behavior and make debugging harder. To improve transparency while preserving resilience, I’ll change the implementation to log a warning via error_log() whenever a pattern fails to compile, instead of failing silently. That way:

Developers are notified of the issue (e.g., in error logs)

The processor continues working safely without interrupting log flow

Let me know if you’d prefer an alternative approach (like throwing on invalid regex only in a strict/dev mode).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the best would be to run this pattern check once in the constructor, and if anything returns false you simply throw an InvalidArgumentException. That is how we handle all other misconfigurations in Monolog.

return $sanitized;
}

if (is_string($value) && $this->patterns) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check on $this->patterns is not needed. Iterating over an empty array woks fine.

Co-authored-by: Christophe Coevoet <stof@notk.org>
@Seldaek Seldaek added this to the 3.x milestone Oct 23, 2025
* @param string[] $patterns PCRE regex patterns to mask inside string values (e.g. '/Bearer\\s+[A-Za-z0-9\\._-]+/')
* @param string $mask replacement token
*/
public function __construct(array $sensitiveKeys = ['password','passwd','pwd','secret','token','api_key','apikey','authorization','auth','cookie'], array $patterns = [], string $mask = 'REDACTED')
Copy link
Owner

@Seldaek Seldaek Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea.. not sure if good or bad, but this should catch any long word of 30+ [a-z0-9] characters (optionally prefixed by foo_bar_ or the like, like many API tokens are), which is very likely to be a secret/token.. But of course it risks some collateral damage.

Suggested change
public function __construct(array $sensitiveKeys = ['password','passwd','pwd','secret','token','api_key','apikey','authorization','auth','cookie'], array $patterns = [], string $mask = 'REDACTED')
public function __construct(array $sensitiveKeys = ['password','passwd','pwd','secret','token','api_key','apikey','authorization','auth','cookie'], array $patterns = ['{\b(?:[a-z]+_)*[a-zA-Z0-9]{30,}\b}'], string $mask = 'REDACTED')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants