empiretwo / gaze-laravel
Redact PII, PHI & secrets before they reach OpenAI/Anthropic/Gemini — one Gaze::clean()/Gaze::restore() call. Reversible, signed-encrypted pseudonymization for Laravel LLM & agent pipelines (GDPR/EU AI Act).
Package info
github.com/CertaMesh/gaze-laravel
Type:composer-plugin
pkg:composer/empiretwo/gaze-laravel
Fund package maintenance!
Requires
- php: ^8.2
- composer-plugin-api: ^2.0
- ext-json: *
- ext-phar: *
- illuminate/console: ^11.0|^12.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/encryption: ^11.0|^12.0
- illuminate/process: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- symfony/http-client: ^7.0
- symfony/process: ^7.0 || ^8.0
- yosymfony/toml: ^1.0
Requires (Dev)
- composer/composer: ^2.0
- laravel/pint: ^1.17
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/phpstan: ^1.11
This package is auto-updated.
Last update: 2026-06-23 10:19:50 UTC
README
Pseudonymize PII / PHI / secrets before they cross the LLM boundary — one
Gaze::clean()call out,Gaze::restore()back, fully reversible owner-side.
use CertaMesh\Gaze\Facades\Gaze; // 1. Strip PII / PHI / secrets before the prompt leaves your app. $session = Gaze::clean($request->string('body')); // 2. Send only pseudonymized text across the model boundary. $reply = $llm->complete($session->cleanText); // 3. Restore the real values owner-side, once the model has replied. return Gaze::restore($session, $reply);
The model never sees real data, yet your app restores it losslessly — tokens map back through a signed, encrypted-at-rest session blob. The same call runs inside queues and long-lived agent loops, not just a single HTTP request, and subprocess failures arrive as typed, exit-bucketed exceptions.
gaze-laravel is the Laravel adapter for the gaze
CLI contract. Detection logic lives upstream in Rust — this package never
re-implements pseudonymization in PHP.
Contents
- Requirements
- Installation — start here
- Usage
- How is this different?
- Advanced surfaces
- Documentation
- Security
Requirements
- PHP
^8.2 - Laravel
^11.0 || ^12.0 || ^13.0 - The
gazebinary —gaze:installfetches the pinned build for you (see below), or supply your own onPATH, invendor/bin/gaze, or viaGAZE_BINARY.
Installation
Two steps:
composer require empiretwo/gaze-laravel php artisan gaze:install
php artisan gaze:install is the canonical setup path. It provisions the app
end-to-end and finishes on a gaze:doctor green-check:
- downloads the pinned gaze binary into
vendor/bin/, - publishes the config and writes a sane default
policy.toml(never clobbering one you've already edited), - optionally installs the NER model (~184 MB, ONNX-backed),
- optionally wires a safety-net backend (OPF or Kiji).
It is idempotent — safe to re-run. A failed run rolls .env back to its
pre-install state.
Non-interactive / CI
Headless runs skip every prompt; the safety-net defaults to none unless you
opt in:
php artisan gaze:install --no-interaction --safety-net=opf
Common flags (php artisan gaze:install --help lists them all):
| Flag | Effect |
|---|---|
--skip-binary |
Don't download the gaze binary |
--skip-ner |
Don't download the NER model |
--safety-net=opf|kiji|none |
Pick the safety-net backend non-interactively |
--force |
Re-run already-done steps (re-download binary, re-fetch NER) |
--force-policy |
Also overwrite an existing policy.toml (destructive — off by default) |
--no-doctor |
Skip the final gaze:doctor gate |
Sub-commands
The umbrella composes three standalone commands you can run on their own for finer control:
gaze:install:binary— install the pinned gaze binary intovendor/bin/.gaze:install:ner— download the pinned ONNX NER model and wirepolicy.toml(legacy alias:gaze:install-ner).gaze:install:safety-net— wire anopforkijibackend into.env.
Composer plugin (optional)
The package also ships a Composer plugin
(CertaMesh\Gaze\Install\GazeInstallerPlugin) that auto-downloads the binary on
composer install. It is optional — gaze:install is the canonical path —
but remains available for adopters who prefer the binary to land automatically.
On first install Composer asks whether to allow the plugin; pick y to enable
auto-download, or n and provision the binary yourself.
For the CI allow-list, the GAZE_SKIP_BINARY_DOWNLOAD / GAZE_VERSION /
GAZE_RELEASE_BASE env overrides, and the full config reference, see
Configuration.
New here? Walk through the getting started guide.
Usage
use CertaMesh\Gaze\Facades\Gaze; $session = Gaze::clean($request->string('body')); $reply = $llm->complete($session->cleanText); return Gaze::restore($session, $reply);
See examples/clean-before-openai.php for a
runnable clean → OpenAI → restore example.
Per-rule detection entries
GazeSession::$entries exposes each tokenized span as a readonly Entry DTO
(class, raw, token, family). The array is empty for upstream releases
that don't surface the field, so consumers can always iterate safely:
foreach ($session->entries as $entry) { logger()->info('detected', [ 'class' => $entry->class, 'token' => $entry->token, 'family' => $entry->family, ]); }
See Exceptions for the exit-bucket reference and Testing for fakes, assertions, and integration setup.
How is this different from regex / generic anonymization libraries?
| Regex / generic anonymization libs | gaze-laravel |
|
|---|---|---|
| Detection | Hand-maintained PHP regex, usually English-centric | Upstream Rust gaze — NER + validated rulepacks + locale packs, never re-implemented in PHP |
| Reversibility | One-way redaction; the original is gone | Tokens map back owner-side through a signed, encrypted-at-rest session blob |
| Failures | Generic exceptions or silent pass-through | Typed exceptions bucketed by exit class (caller / config / integrity / infra) |
| Runtime fit | Built for a single HTTP request | Queue-aware, with a long-lived daemon for multi-turn agent loops |
Not what you're after? This is not in-process PHP detection — the Rust crate
is the source of truth — and not a generic subprocess wrapper; it is specific to
gaze. See the North Star non-goals.
Advanced surfaces
Opt-in surfaces — reach for them once the basic clean / restore round-trip is in place:
- HTTP proxy daemon — pseudonymizes requests bound for OpenAI / Anthropic / Gemini and restores their replies.
- JSONL stdio daemon — low-latency
Gaze::daemon()runtime for agent loops and worker queues, with no per-turn binary startup. - Kiji safety-net backend — Tier 2.5 DistilBERT NER subprocess for higher-recall Pass-3 leak detection.
Documentation
- Documentation index
- Getting started
- Configuration
- Architecture
- NER detection
- Blob lifecycle
- Security model
- Exceptions
- Diagnostics
- Testing
- Queue integration
- Retry discipline
- Livewire integration
- Conversational-loop patterns
- Operations
- Audit query / export
- Proxy daemon
- Daemon (JSONL stdio)
- SafetyNet (OPF + Kiji)
- Upgrading
- Upstream coverage
Security
Session blobs are encrypted at rest with Laravel's encrypter, keyed by
GAZE_ENCRYPTION_KEY or APP_KEY. Only the pseudonymized $session->cleanText
should cross the model boundary; restore happens owner-side. See the
Security model for guarantees, responsibilities,
and compliance boundaries.
Upgrading
Per-minor walkthroughs live in docs/how-to/upgrading.md;
pair them with the upstream binary's
UPGRADE.md. The current
pin is v0.11.1 — see the v0.9.0 → v0.11.1 section for adoption notes, the
opt-in restore-telemetry surface (and its audit-trail-not-DLP caveat), and the NER
fail-closed / byte-exact restore rationale.
Known limitations
- Pre-built binary auto-downloads currently cover Linux x86_64 and macOS arm64.
Intel Mac users must install
gazefrom source and setGAZE_BINARY. - NER model artifacts are not bundled in the Composer package.
gaze:installfetches them on demand (or rungaze:install:nerdirectly); pass--skip-nerto defer.
License
Apache-2.0 — see LICENSE.