kirschbaum-development/redactor
Redactor is a Laravel package for redacting sensitive data before it reaches logs, APIs, or users. Define rules to mask or remove emails, tokens, and other private values from models, arrays, and output with minimal configuration.
Architecture fit. The package is Laravel-native in the way that matters: a service provider, a facade (Redactor), a publishable profile-based config, Artisan commands (redactor:scan, redactor:validate, redactor:allow), middleware (redact:profile), an event (RedactionPerformed), and contracts (Detector, TokenStore, RedactionPerformed) that allow substitution. The most important design decision is the split between detection (what was found, where, with what confidence) and operators (what happens to it — redact, mask, partial, hash, surrogate, tokenize, nullify, preserve). That separation is what makes the package tunable by policy rather than by editing regexes, and it is the seam a TPM should evaluate hardest, because it is where product requirements ("keep the last four digits", "keep logs joinable", "keep the field typed") actually land.
Where it sits. This is an in-process library, not a data-plane proxy. Coverage is therefore opt-in per surface: HTTP responses, log records, streamed output, file scans, AI prompts and MCP tool results are addressed; database writes, queue payloads, outbound third-party SDK calls and storage are not, unless you instrument them yourself. A TPM should treat the first deliverable as a data-flow map with the redaction surface marked, not a package install.
Integration feasibility. Installing and publishing config is hours. Getting to a defensible production posture — profile selection, allowlists, custom rules with samples and counter-samples, a ruleset baseline, key management, and an evaluation corpus — is weeks of engineering plus a security review. Nothing in the package blocks that work; the effort is in policy, not plumbing.
Technical risk.
redactSafely(), fail-closed middleware, sidecar circuit breaker, "an allowlist entry that cannot be evaluated allows nothing") is directionally correct but must be verified against your own payloads.--verify, Presidio /analyze, ONNX). No-egress guarantees must be confirmed in code review, not inferred from documentation.Key questions.
Redactor::fake() swap cleanly in the container, and can assertNeverEmitted() be wired into CI as a release gate?redact:profile per content type, and can it be made fail-open per route?surrogate/hash supplied and rotated, and what happens to joinability when they rotate?Stack fit. Laravel monolith or modular monolith, PHP 8.2+, with a shared cache (Redis) and a CI pipeline that can emit SARIF. It fits cleanly into an existing Laravel app; it does not fit a non-Laravel service or a polyglot estate without per-language equivalents. Confirm constraint compatibility against your matrix before committing, and validate config:cache behaviour — profiles are resolved from config and cached, so a deploy that caches config must invalidate the resolved-profile cache correctly.
Migration path. There is usually an incumbent: ad-hoc regex scrubbing in a logging handler, a log-shipper-side scrubber, or nothing. Do not cut over. Run the package's scanner in read-only mode against historical logs and real fixtures to produce a findings corpus, then run it alongside the incumbent and diff outputs before retiring anything.
Sequencing (lowest risk first).
redactor:scan with SARIF, a committed baseline, redactor:validate and the self-testing rule samples as a pipeline gate. Zero runtime exposure, and it generates the evidence base for everything later.detokenize, only where joinability is a stated requirement — this introduces key custody and cache-TTL as new operational dependencies.Compatibility and coexistence. Publish and review the config as a versioned artifact, not a generated blob. Add redactor:validate to CI so a broken profile fails the build rather than production. Track the ruleset fingerprint across upgrades and expect baselines to need regeneration when rules change; treat a fingerprint mismatch as a review trigger. Keep the incumbent scrubber active until you have a measured false-negative comparison, not just green tests.
Exit criteria per step. Coverage measured against a labelled corpus; false-positive rate on real traffic below an agreed threshold; no increase in 5xx; and a documented rollback (config revert plus flag off).
Maintenance. Low-to-moderate code maintenance, moderate policy maintenance. The package itself is pinned and, at this size, forkable under MIT. The ongoing cost is the rule set: every new secret format, every false positive, every codename added to a dictionary rule is a reviewed change. Budget roughly 0.1–0.2 FTE for ongoing ownership, front-loaded at roughly 1 engineer-week for CI scanning plus logs, and 3–4 weeks for middleware, custom rules, corpus and security review.
Support. The vendor is an agency; no commercial SLA is stated. Assume best-effort community support, with "fork it" as the real escalation path. That is acceptable for a bounded, well-tested integration and unacceptable if redaction becomes a regulated control without an internal owner who understands the ruleset.
Scaling. Per-request cost is on the order of tens of microseconds for a small payload (~68µs for a 31-leaf payload through the default profile), which is noise at typical request rates — a few percent of a core at 2k rps. Batch surfaces dominate instead: file and repository scanning scales linearly with size, and streaming keeps memory flat via overlapping windows, so large-file scanning is bounded by wall-clock, not RAM. The real scaling risk is rule count × payload breadth in profiles that accumulate custom rules; profile caching and path-rule tries mitigate this, but verify with your own rules rather than the shipped twenty. Multi-node deployments must share the token store and the config cache; a per-node in-memory cache will produce divergent detokenization.
Failure modes to design for.
| Mode | Symptom | Mitigation |
|---|---|---|
| Fail-closed middleware | 500s on affected routes | Feature-flag rollout; per-route profile; error-budget review |
| Over-redaction | Timestamps, order IDs, hashes replaced; logs unusable | Confidence thresholds, keywords/min_length prefilters, allowlists; monitor volume |
| Under-redaction | Novel secret format passes; decoded payloads beyond one layer | Corpus-driven rule additions; redactor:validate gate; periodic re-scan of logs |
| Missing HMAC key | Silent fallback to plain redaction, breaking joins | Key-presence health check; alert on surrogate rate dropping to zero |
| Token store eviction | detokenize returns unknown tokens |
Shared Redis with TTL > max replay horizon; assert on unknown-token rate |
| Sidecar unavailable | Degrades to rules-only | Circuit breaker already present; alert on entity-recognition yield |
| Egress surprise | --verify/Presidio contact external hosts |
Code review + allowlist; keep both off by default |
| Baseline drift | CI passes while rules changed | Ruleset fingerprint comparison on every upgrade |
Observability. Subscribe to RedactionPerformed (profile, keys, counts per rule and entity — never values) and alert on rate collapse as well as rate spikes: a detector silently failing looks exactly like a quiet day. Pair that with Redactor::fake() assertions in application tests so "the secret never left" is proven in CI rather than hoped for in review.
Ramp-up. The documentation reads as release notes rather than task-oriented guidance — excellent on capability and rationale, weak on "here is the twenty-line config for a Laravel API." Plan for an internal spike to produce your own runbook, a starter profile, and a labelled corpus before broad rollout; that artifact, not the package docs, will be what on-call actually uses.
How can I help you explore Laravel packages today?