Expand description
Edge DLP — PII / secret detection and redaction (gateway L3).
Extends the WAF-lite idea (crate::waf) from “is this request an attack” to “does this
payload contain data that must not leave (or arrive)”. Three detector families, fastest-first:
- signature detectors — linear-time
regexpatterns for well-shaped secrets and PII (provider keys, AWS keys, private-key blocks, emails, card-like numbers, SSNs, phones, IBANs). A signature may carry a post-match validator (e.g. the Luhn check for card numbers) so a digit run that doesn’t checksum is not flagged. - a gazetteer detector — an
aho_corasickautomaton over an operator-supplied term list (known customer names, project codenames, internal identifiers). Linear-time, many-term, the dictionary half Presidio leans on a deny-list for. - an entropy detector — flags long, high-Shannon-entropy tokens that look like credentials but match no signature (a catch-all; off by default since it can false-positive).
When the optional ner cargo feature is built, a fourth family — a small ONNX NER model
(GLiNER / DeBERTa class, via the pure-Rust [edgeguard_ner] crate) — runs over the buffered text
to catch the entities regex can’t: person, address, org. The NER family is the slow part and
is never run on the streaming path; the always-present signature/gazetteer/entropy fast path is
what actually enforces on a stream. With the ner feature off, the engine is byte-for-byte the
deterministic-only engine and pulls no ML dependency (the single-static-binary promise).
Four modes, a report-first rollout ladder:
off— disabled.report— detect, count, log; pass the payload through unchanged.block— a request with a finding is rejected403; a response is withheld.redact— each finding’s span is rewritten per the configuredRedactStyle; the payload flows on. The default style is[REDACTED:<category>];maskkeeps the last four characters,hashemits a stable opaque token so the same value redacts identically everywhere.
The engine here is pure (no I/O on the deterministic path): scan returns the
findings and redact rewrites them. The proxy applies it to the inbound
request body and the (buffered) response body; streaming responses are scanned frame-by-frame with
a carry buffer so a secret split across two SSE frames is still caught.
All regexes are the linear-time regex crate (no backtracking), so a crafted payload can’t cause
catastrophic blowup — the same ReDoS-safety the WAF relies on.
Structs§
- DlpEngine
- The compiled DLP engine. Built once per config (re)load and carried on the proxy
Runtime. - Finding
- One detected span:
[start, end)byte offsets into the scanned text, its category, and a detector confidence in[0.0, 1.0]. Deterministic detectors (signature / gazetteer / entropy) always report1.0; the NER family reports the model’s per-entity probability so it can be thresholded and audited. - MaskMap
- A reversible mask map: the placeholder↔original mapping built while redacting an inbound
request, used to unmask the response (buffered and streamed) back to the caller’s own values.
The provider only ever sees placeholders; the client gets its data restored — the round-trip an
irreversible
[REDACTED]tag (and any unmask that can restore another caller’s value) cannot do.
Enums§
- DlpMode
- What to do when a payload has a finding.
- Redact
Style - How a redacted span is rewritten in
redactmode.
Constants§
- CATEGORIES
- Stable category labels (also the metric label and the
[REDACTED:<category>]tag). A fixed set, so the metric cardinality is bounded. Keep in sync withDLP_CATEGORIESincrate::metrics.