beops
all projects

hider

tiered redaction gateway for log bundles on their way to an llm provider — presidio patterns, local ner over the residue, and a proxy that refuses to forward what it could not clean.
draft aisecuritypython
year
2026
role
engineer
stack
python · microsoft presidio · spaCy · stdlib http
link

a support engineer with a 400 mb log bundle wants to ask a model what the logs say. doing that raw sends session ids, mailbox addresses, tenant names and sometimes live bearer tokens to a vendor nobody signed a data agreement with. blocking it does not work, people paste excerpts anyway. so the request path gets a redaction layer instead, and the layer has to be good enough that the answers stay useful or it gets bypassed too.

what it does

  1. tier 1, deterministic — presidio pattern recognizers plus custom ones for shapes generic pii tooling has never heard of: worker pod ids, tenant-bearing saas urls, pre-signed storage links, bearer tokens. no network, no model, ~139 microseconds per line.
  2. tier 2, local ner — presidio’s spaCy engine over only what tier 1 left behind, catching names and organizations in prose. in-process, so nothing about a name ever leaves the machine.
  3. the gateway — an openai-compatible proxy that redacts chat completions in flight, refuses degraded requests unforwarded (503, nothing sent), and turns pseudonyms back into real values in the reply for the caller who is allowed to see them.

what makes it defensible

  • measured, not claimed: 99.9% recall on shaped identifiers, 86.7% on free-text names against a 0.75 floor, 0.0% false-redaction on 1,000 known-clean lines. deterministic tiers are held to zero unexplained survivors; the model tier is held to a floor with every miss listed.
  • fail-closed invariants enforced by 130 tests: a tier that errors marks the unit degraded instead of passing it through, and the gateway refuses degraded units outright.
  • credentials are destroyed, identifiers are pseudonymised, decided by the entity spec never the call site. the vault refuses to store a secret at all.
  • adversarial corpus of 16 hostile placements (identifiers split across lines, percent-encoded, buried in escaped json) where each case declares in advance whether it should be caught, so a known gap cannot dress up as a pass.