Laravel Magazine
Str::mask() and Friends: Redacting Sensitive Data Before It Hits Your Logs

Str::mask() and Friends: Redacting Sensitive Data Before It Hits Your Logs

Eric Van Johnson ·

It is easy to log a request payload for debugging and forget it contains a full credit card number, an SSN, or a customer's email address in plain text. Laravel's Str::mask() helper makes it trivial to redact the sensitive part of a string before it ever reaches a log driver, without writing your own substring logic.

The basics

use Illuminate\Support\Str;

Str::mask('4242424242424242', '*', 4);
// '4242************'

Str::mask('taylor@laravel.com', '*', 3);
// 'tay****************'

The second argument is a positive index counting from the start, so 4 means "keep the first 4 characters, mask the rest."

Masking from the end

Pass a negative starting index to mask from the end of the string instead, which is the more common pattern for card numbers, where you want to show the last four digits and hide everything before them:

Str::mask('4242424242424242', '*', 0, -4);
// '************4242'

The fourth argument caps how many characters get masked, so here it masks everything except the final 4 characters.

Masking the middle of a string

Str::mask('taylor@laravel.com', '*', 3, -10);
// 'tay**********m.com'

Combining a positive start and negative length lets you keep both ends visible while hiding the middle, handy for things like partially redacted emails in an admin support panel.

Applying it before logging a request payload

namespace App\Http\Middleware;

use Illuminate\Support\Str;

class LogSanitizedRequest
{
    public function handle($request, \Closure $next)
    {
        $payload = $request->all();

        if (isset($payload['card_number'])) {
            $payload['card_number'] = Str::mask($payload['card_number'], '*', 0, -4);
        }

        if (isset($payload['ssn'])) {
            $payload['ssn'] = Str::mask($payload['ssn'], '*', 0, -4);
        }

        logger()->info('Incoming payment request', $payload);

        return $next($request);
    }
}

Don't forget your log channel's own scrubbing

Str::mask() handles values you explicitly choose to redact, but it's worth pairing with a broader safety net. Laravel's logging config lets you register a custom formatter or processor on a channel to catch fields you might miss by hand:

// config/logging.php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['single'],
        'tap' => [\App\Logging\RedactSensitiveFields::class],
    ],
],

Why this deserves five minutes of your time

Every team has a story about a card number, password, or auth token showing up in a log aggregator because someone logged $request->all() during a debugging session and forgot to remove it before merging. Str::mask() costs one line at the point you log something sensitive, and it turns "we have PCI-relevant data sitting in Papertrail" into a non-event. Given how cheap it is to apply, there's not much reason for a payment or auth-adjacent code path to skip it.

Stay Updated

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.