Expand description
PII masking — classification-driven redaction at every data sink.
§Why this is load-bearing for the rest of the framework
The compliance machinery shipped so far makes data durable on purpose: the audit trail is hash-chained append-only, outbox rows are committed, idempotency caches replay responses for hours, and the DLQ parks full payloads. Every one of those becomes a permanent PII leak if a card number or patient name reaches it raw — you cannot delete from a sealed ledger without destroying the very tamper-evidence it exists to provide. Masking therefore happens at the sink, before durability.
§Zero-lock mechanics
The rule set lives behind one ArcSwap<MaskingPolicy> (the proven
pattern from secrets / tenants / policies): applying masks is one atomic
pointer load plus a pure walk over the JSON tree against paths that were
parsed at load time — no regex, no I/O, no locks on any hot path.
§Usage
// boot — global rules by data classification:
ctx.provide(Masker::new(
MaskingPolicy::new(1)
.field("email") // Redact (default)
.field("payment.card.number:last4") // PCI PAN
.field("ssn:drop")
.field("items.*.patient_name:hash"), // joinable, unreadable
));
// route — extra fields for this endpoint only:
#[Get("/:id", security("bearer"))]
#[MaskFields("notes.*.body", "phone:last4")]
async fn get_patient(/* ... */) -> Result<Json<Patient>, HttpException> { /* ... */ }No Masker in the DI container → every sink passes data through
untouched (dev mode needs zero configuration).
Structs§
- Mask
Rule - A field rule: where + how.
- Masker
- Hot-swappable redaction point. Provide via
ctx.provide(Masker::new(…)). - Masking
Policy - Versioned, immutable rule set (hot-swapped whole, like
PolicySet).
Enums§
- Mask
Strategy - How a matched field is transformed.
- PathSeg
- One segment of a compiled field path.
*matches every array element (or every object value), enabling rules likeitems.*.email.