leocavalcante/redact-sensitive
Safely redact sensitive data like passwords, tokens, and personal details from strings and arrays. A lightweight PHP package offering a simple API to mask values before logging, debugging, or returning API responses, with easy integration into Laravel applications.
Install the package with Composer:
composer require leocavalcante/redact-sensitive
Then look at, in order:
vendor/leocavalcante/redact-sensitive/README.md for the supported constructor/config API.vendor/leocavalcante/redact-sensitive/src/ — this package is small, so read the whole processor once. Confirm whether it accepts a key list, uses defaults, and what replacement token it writes.composer.json for the Monolog and PHP version constraints, especially if you are on Laravel 11 / Monolog 3.The core integration is always the same: create or obtain a Monolog logger, push the processor, and log sensitive values as context keys.
use Leocavalcante\RedactSensitive\RedactSensitiveProcessor;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
$logger = new Logger('app');
$logger->pushHandler(new StreamHandler(__DIR__.'/app.log'));
$logger->pushProcessor(new RedactSensitiveProcessor([
'password',
'password_confirmation',
'token',
'authorization',
]));
$logger->info('login attempt', [
'email' => '[email protected]',
'password' => 'hunter2',
]);
Inspect the resulting log line. The password value should no longer be hunter2. If your installed version uses a different constructor name or a config file, follow the README; the important first use case is validating that a known secret disappears from the output.
For Laravel, the quickest wiring is on a channel in config/logging.php:
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'processors' => [
\Leocavalcante\RedactSensitive\RedactSensitiveProcessor::class,
],
],
If the processor needs constructor arguments, use a tap class instead of instantiating it directly in config.
Use a central tap for the key list. Keep the sensitive-key list in one place so channels do not drift. A tap is also safe for config:cache, unlike closures or object instances in config files.
namespace App\Logging;
use Leocavalcante\RedactSensitive\RedactSensitiveProcessor;
use Monolog\Logger;
final class RedactSensitiveTap
{
public function __invoke(Logger $logger): void
{
$logger->pushProcessor(new RedactSensitiveProcessor([
'password',
'password_confirmation',
'current_password',
'token',
'access_token',
'refresh_token',
'api_key',
'secret',
'authorization',
'cookie',
'credit_card',
'cvv',
]));
}
}
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'tap' => [\App\Logging\RedactSensitiveTap::class],
],
Treat the key list as an application contract. Align it with request validation rules, API payload fields, and job payloads. Add aliases your app actually uses: passwd, pwd, auth, bearer, client_secret, private_key, session, remember_token. Review it when new integrations are added.
Keep context deliberately shaped. The processor protects keys, not arbitrary strings. Do not pass $request->all() into a log call and hope for the best. Pass validated, explicit context:
Log::info('payment failed', [
'user_id' => $user->id,
'gateway' => 'stripe',
'card_last_four' => $lastFour,
'error_code' => $exception->getCode(),
]);
Use Log::withContext() for correlation data such as request IDs and tenant IDs, not for secrets.
Test the redaction path. Use a Monolog TestHandler so processors actually run:
use Monolog\Handler\TestHandler;
use Monolog\Logger;
$logger = new Logger('test');
$handler = new TestHandler();
$logger->pushHandler($handler);
$logger->pushProcessor(new RedactSensitiveProcessor(['password']));
$logger->info('login', ['password' => 'hunter2']);
$this->assertStringNotContainsString('hunter2', json_encode($handler->getRecords()));
Also test nested arrays, extra data, and objects if your logging pipeline can produce them.
Apply it to every long-lived logger. Monolog processors are attached per logger, not globally. Queue workers, console commands, and custom loggers all need the processor if they can emit sensitive context. In Laravel, centralizing through channel config or a shared tap avoids this.
Configure downstream tools separately. If you ship logs to Sentry, Flare, Bugsnag, Papertrail, or a SIEM, those tools may capture request bodies or exception context independently of Monolog. Redacting Monolog output does not automatically scrub the SDK's own event payloads.
meta.value, embedded in a message string, or interpolated through {password} placeholders can still leak. Avoid PSR-3 placeholders for secrets and keep secrets in explicitly named context keys.zend.exception_ignore_args=On in production, and avoid putting credentials into exception messages.Password, passwd, and pwd may not match password. Add variants explicitly.toArray()/jsonSerialize() before logging, or wrap sensitive values in arrays with recognized keys. Test this rather than assuming recursion.LogRecord; Monolog 2 processors receive an array. Check the package's composer.json and run composer show monolog/monolog. Laravel 11 uses Monolog 3, so verify before adopting.pushProcessor prepends). If another processor injects or interpolates context, verify with a test that redaction runs before the secret reaches the message or formatter.config/logging.php. Use a class name or tap. Object instances in config can break php artisan config:cache or behave unexpectedly after deployment.ProcessorInterface implementation handy. The core behavior is small enough to reproduce in-house if maintenance stalls.jo***@example.com), or a configurable token, subclass the processor or wrap it in your own Monolog processor rather than forking the package. Keep the custom processor tested against nested arrays and both context and extra.How can I help you explore Laravel packages today?