Project record

mastra-pii

PII redaction for Mastra agents as an adapter, not a detector: one interface over a Presidio deployment — the package owns integration, the deployment owns intelligence — with a deterministic Indian-recognizer layer and fail-closed defaults.

In development State since Aug 9, 2026 TypeScript · Mastra (>=1.57) · Presidio · Node.js
Synthetic document study with sensitive lines obscured
Fail-closed redaction — structure remains while content is withheld.

Current state. Live on npm as v0.2.0-alpha.4 with provenance attestation. One interface, two analyzers: the RemotePresidioAdapter over a deployed Presidio container (spaCy NER + per-request Indian recognizers) and a zero-dependency deterministic engine that doubles as the outage fallback. Extension path: recognizer pass-through (#25). Remaining P2 hardening in #19.

Context

The package owns the integration; the deployment owns the intelligence. mastra-pii is an adapter over a Presidio deployment — not a detector — and its story is compatibility and ease of use, not engine accuracy.

Problem

An agent loop touches sensitive text at many surfaces — prompts, tool inputs and outputs, traces, logs, memory — and every one of them needs redaction coverage. The consumer is an agent application whose users write Hinglish and share Indian identifiers (Aadhaar, PAN, UPI, IFSC, bank accounts); Western redaction libraries optimize for English prose and US formats, so off-the-shelf coverage was never the answer. The hard problem that remains is integration: wiring a redaction layer into a Mastra agent normally means custom code at every boundary. The adapter exists so that wiring is zero.

Approach

  1. One interface, two analyzers. RemotePresidioAdapter talks to a deployed Presidio container (spaCy NER by default, plus per-request Indian recognizers); LocalFallbackAdapter is the zero-dependency deterministic engine that takes over on outage. Swap deployments without changing app code.
  2. The deterministic layer ships with the package. INDIAN_DEFAULTS — Indian regex recognizers with client-side Verhoeff (Aadhaar) and Luhn (cards) checksums, a curated entity allowlist, and shape post-filters — sent as per-request ad_hoc_recognizers, and runnable standalone with no deployment at all.
  3. Drop-in Mastra integration. pii.processor implements Mastra’s Processor interface — inputProcessors: [pii.processor] plus outputProcessors is the whole integration, with a third hook (processLLMRequest) covering every model call including tool continuations. Placeholders stay type-tagged ([PAN_1]); custom regexes run in time-bounded worker sandboxes.
  4. Fail-closed by default. Outages degrade to the local engine (or [REDACTION_FAILED] in strict mode); raw PII never leaks.
  5. Layer boundaries explicit. The package accepts only the deterministic layer; ner and model requests are rejected. Adding a model layer or custom recognizers is a deployment-side decision.

Roadmap

Built-in recognizer expansion (#9#13) and obfuscation canonicalization (#8) are closed as out of scope (2026-08-14) — detection extensions flow through recognizer pass-through, not new built-ins. The remaining P2 hardening is tracked in the master issue #19, ordered for implementation — pick the first open one, top to bottom:

  • #14 — Presidio adapter: retries with jitter + timeout config
  • #25 — Recognizer pass-through: user recognizers (Presidio shape) replace the adapter’s recognizer set; custom patterns (local shape) run in both modes — the documented way to add detection
  • #16 — Observability: per-entity redaction counters
  • #15 — Agent-callable redactPII Mastra tool
  • #17 — Structured-document inspection (redact inside JSON/objects)
  • #18 — Mastra compatibility matrix (1.57 → latest 1.x) — final guard

Evidence

The package is live on npm (provenance-attested), the README documents the full contract, and the repo is public — src/ for the adapter internals, deploy/ for the Presidio recipe. Comparison harnesses live in docs/evaluation/ for internal use; accuracy is the deployed analyzer’s property, not this package’s headline. Portfolio examples are synthetic fixtures only.

Decisions

The published ADR records the starting decision: deterministic local redaction with fail-closed behavior before broader model-backed promises. The pivot to a remote Presidio analyzer was gated on an internal spike before any package work.

Outcome / current state

The package is live on npm (0.2.0-alpha.4, provenance-attested) with 67 passing tests and a clean-consumer verification gate. What it does not do is documented as loudly as what it does:

  • Obfuscated formats still defeat every engine — a stated permanent limit, not a backlog item.
  • Names and addresses are NER-only, deployment-side.
  • International identifiers have no built-in recognizers; pass-through is the extension path.
  • The model layer is explicitly out of scope.

Those limits are scope, not surprises.

Next

The hardening backlog (#19) is the ordered plan: retries with jitter, recognizer pass-through, observability, the agent-callable tool, structured-document inspection, and finally the Mastra compatibility matrix. Release cadence bundles pairs per alpha — alpha.5 = #14 + #25, alpha.6 = #16 + #15, alpha.7 = #17 + #18, then stable 0.2.0 once the matrix is green. Every alpha ships through the existing release workflow (trusted publishing, provenance attestation) — no token publishes.