directorytree/privacy-filter
Laravel wrapper around the privacy-filter.cpp binary that installs the compiled executable and GGUF model, then detects private entities in text through a small PHP API.
Architecture fit
privacy-filter.cpp CLI binary and a GGUF model. There is no daemon, no HTTP service, and no persistent in-process engine: every classification spawns the native process, which loads the model into memory, emits entities, and exits.PrivacyFilter::entities() facade, PrivacyFilter::fake() for test isolation, and documented Horizon/queue guidance. That makes it cheap to adopt and easy to hide behind an app-level interface.type (raw string), text, byte start/end, score, length(), plus an EntityType enum that returns null for unrecognized types. Small surface area is good for wrapping; the null fallback is a schema-drift hazard you must handle explicitly.Integration feasibility
composer require directorytree/privacy-filter then php artisan privacy-filter:install. That installs artifacts, not just code — an OS/architecture-matched binary from a GitHub release and a multi-GB .gguf from Hugging Face, both defaulting to storage/app/privacy-filter/.--url on both install-binary and install-model lets you host artifacts internally for air-gapped or audited environments; --release pins the binary; config exposes paths, process.timeout (default 60s), model.url, and release.repository/version. All five are environment-overridable.Technical risk
v1.0.0 default), and the model URL/version drift independently. Nothing in the wrapper appears to enforce compatibility between them. A model swap without a binary bump is a silent behavioral change.--force is required to overwrite. On a long-lived VM, a release bump can appear to succeed while running the old binary. On an immutable image, this is a non-issue — which argues for baking artifacts at build time.v1.0.0 → v1.1.0 only. This is adoptable but not a bet you make without an exit plan.start/end/length() are byte-oriented. Any redaction using mb_* functions or character-based slicing will corrupt multi-byte text. The README's str_replace example is a demo, not an implementation — it replaces all occurrences of a string, not the flagged ranges.Key questions
entities() call, and how much of it is model load versus inference? Does the native binary support a persistent/server mode the wrapper doesn't expose?EntityType vocabulary, and how frequently does the upstream binary add types that this wrapper will render as null?Stack fit
tries => 1, timeout => 300.Migration path
Redactor contract with entities() and redact(), bind the package to it, and treat the package as one implementation. This costs an hour and converts a risky native dependency into a swappable detail.str_replace. Sort by start, drop overlapping ranges, and apply substr_replace from the end backwards so earlier byte offsets stay valid.Compatibility
PRIVACY_FILTER_BINARY_PATH / PRIVACY_FILTER_MODEL_PATH at the baked locations; keep the install commands for local dev only. This eliminates runtime network dependency, makes the deploy reproducible, and neutralizes the silent-skip behavior.PRIVACY_FILTER_BINARY_VERSION, and a model URL containing an immutable revision — and treat the tuple as a single deployable unit. Record it in the release notes.Sequencing
entities() against 200–500 real records from your own data, measure latency, peak RSS, and quality (precision/recall against a hand-labeled sample, at 0.5 and 0.75 thresholds). Gate everything on entity quality against your vocabulary, not the demo.privacy-filter queue, processes sized to available RAM ÷ model RSS, retries disabled or tightly bounded, dead-letter queue for failures.Maintenance
privacy-filter.cpp and the model have their own release cadence over which this package has no control. You inherit their breaking changes.Support
null, and artifact version drift after a deploy.Scaling
processes conservatively and cap the queue hard — a burst of enqueued jobs with an uncapped pool is an OOM event, not a slowdown.Failure modes
| Failure | Symptom | Mitigation |
|---|---|---|
| Artifact missing after deploy | Hard exception on first job | Startup assertion + health-check command |
| Installer silently skipped upgrade | Old binary running with new config | Bake at build time; --force on VMs |
| Architecture/libc mismatch | Exec format or loader error | Pin base image; verify in CI |
| Download failure at deploy | Install command non-zero | Bake artifacts; internal mirror via --url |
| Process timeout (default 60s) | Job fails; possible orphan process | Raise/align timeout with queue timeout; verify reaping |
| Model load OOM | Worker killed, job retried, another worker dies | Cap processes; disable or bound retries |
| Unrecognized entity type | type() returns null, silent miss |
Log unknown raw types; alert on novel values |
| False negatives on your vocabulary | PII survives redaction | Threshold tuning + labeled regression set |
| False positives | Over-redaction, degraded usability | Threshold tuning; entity-type allowlist |
| Multi-byte slicing bug | Corrupted characters in redacted output | Offset-based substr_replace, never mb_* |
Ramp-up
How can I help you explore Laravel packages today?