lucajackal85/pii-sanitizer-php
Detect and redact personally identifiable information in PHP and Laravel applications. This package helps sanitize emails, phone numbers, names, and other sensitive data before logging, storing, or sharing, using configurable rules to reduce privacy and compliance risk.
jackal/pii-sanitizer (v0.2.0) is a small, single-purpose package for scrubbing personally identifiable information before it lands somewhere durable — logs, queued payloads, audit tables, exception reports. It is early-stage (0.x), so treat the public API as fluid and pin an exact version in composer.json.
Install and confirm the namespace first. v0.2.0 renames the root namespace to Jackal\PiiSanitizer. If you are upgrading from 0.1.x, every use statement, config reference, and container binding you wrote against the old namespace will break. Run:
composer require jackal/pii-sanitizer:^0.2.0
composer dump-autoload
php artisan config:clear && php artisan cache:clear
Then grep your codebase for the old namespace before doing anything else:
grep -rn "PiiSanitizer" app/ config/ tests/ --include="*.php"
Where to look first, in order:
vendor/jackal/pii-sanitizer/src/ — the class list is short; read every file. At 0.x you will learn more in ten minutes of reading source than from any docs.register()/boot() — this tells you what is auto-discovered, what is bindable, and whether a config file is publishable (php artisan vendor:publish --provider="Jackal\PiiSanitizer\...").composer.json of the package — check the laravel/illuminate/* constraints and the PHP floor. If it targets Laravel 11+ only, do not try to backport it to a 9.x app.tests/ in the package — the fastest spec of intended behaviour.First use case. Don't start by wiring it into your whole model layer. Start narrow: point it at your logging channel so that user emails, phone numbers, and tokens stop reaching disk. That is the highest-value, lowest-blast-radius integration, and it proves the package works before you trust it with production writes.
Log scrubbing via a Monolog processor. The most common day-to-day pattern is a tap on the logging stack. In config/logging.php, add the package's processor to the tap array of your stack channel (or register it globally in a service provider against the Log facade) so every record's context and message is passed through the sanitizer before the handler writes it. This catches Log::info('...', $request->all()) calls you forgot were there.
Middleware for request/response redaction. Register a global or group middleware that sanitizes the request payload before it hits your validation and audit pipeline, and optionally the JSON response on the way out. Keep it off the api health-check routes; sanitizing on every request is cheap but not free, and it makes stack traces harder to read when something goes wrong.
Model-level masking. Where the package exposes a trait or attribute cast, apply it to the fields that are genuinely sensitive (email, phone, national_id, iban) rather than blanket-applying to the whole model. A masked email breaks login lookups and password resets — mask on serialization (toArray/toJson) and in logs, not in the database column itself. If you need reversibility, the encrypted-cast route (encrypted, encrypted:array) is the Laravel-native answer and you should not expect a sanitizer package to provide it.
Queue and job payload hygiene. Sanitize before dispatch, not after. Wrap the sensitive slice of your job constructor arguments:
MyJob::dispatch($sanitizer->sanitize($payload));
This keeps PII out of the jobs table, out of Horizon's stored payloads, and out of failed-job dumps — three places people routinely forget.
Exception reporting. If you use Sentry, Flare, or Bugsnag, add the sanitizer to the context/before_send hook so request bodies and user context are redacted before they leave your infrastructure. This is usually a five-line integration and it is the one auditors ask about.
Testing the contract. Write your own tests for the fields you care about rather than trusting the package's regexes:
it('redacts emails in log context', function () {
Log::spy();
Log::info('login', ['email' => '[email protected]']);
Log::shouldHaveReceived('info')->withArgs(fn ($m, $c) =>
!str_contains(json_encode($c), '[email protected]')
);
});
At 0.x this is your regression net when 0.3.0 lands.
The 0.2.0 namespace rename is the whole story of this release. It is a hard BC break with no deprecation path and no class aliases. Anything that resolves the old FQCN from a string will fail silently rather than loudly: config files, config/app.php provider arrays, Horizon/Octane config, cached bootstrap files, serialized queued jobs sitting in the jobs table, and anything in bootstrap/cache/*.php. After upgrading, drain or flush the queue and run php artisan optimize:clear. Old serialized jobs referencing classes in the previous namespace will fail to unserialize — expect __PHP_Incomplete_Class errors if you skip this.
Pin the version. "jackal/pii-sanitizer": "0.2.*" — not ^0.2, and certainly not *. Pre-1.0 packages treat namespace and method renames as minor bumps, exactly as this release demonstrates.
Package discovery caching. If the provider is auto-discovered, a stale bootstrap/cache/packages.php can keep pointing at the old namespace after composer update. composer dump-autoload alone does not regenerate it; php artisan package:discover or a full optimize:clear does.
Regex-based redaction is lossy and directional. Most sanitizer implementations are tuned to catch patterns, not to be exhaustive. Expect to miss: names in free text, addresses spread across fields, IDs with unusual formatting, and PII embedded in base64 or JSON-stringified blobs. Never treat a sanitizer as a compliance control on its own — pair it with not collecting the data in the first place and with column-level encryption at rest.
Watch for over-redaction breaking observability. A sanitizer aggressive enough to redact emails will often also mangle UUIDs, order numbers, or IPs you need for debugging. Keep an allowlist of fields/patterns that must survive (trace IDs, session IDs, correlation tokens) and verify it after every upgrade.
Performance in hot paths. If you apply sanitization per-log-record in a high-throughput worker, benchmark it. Deep array recursion on large context payloads is the usual culprit. Cache compiled patterns where the package allows it, and short-circuit on empty context.
Extension points to check before writing your own. Look for a config-driven rule set (patterns, field names, custom callbacks) and an interface you can implement for domain-specific sanitizers. If the package exposes neither, contain your integration behind your own PiiSanitizer wrapper class so the next namespace rename or API change is a one-file edit rather than a codebase-wide search-and-replace — which is precisely the tax this release imposed.
Contributing context. This is a single-maintainer package at 0.2.0 with one PR in the release. Read the diff before adopting it in a regulated codebase, and be prepared to vendor or fork it if the maintenance cadence doesn't match your needs.
How can I help you explore Laravel packages today?