symfonic.services.shadow.redaction¶
redaction ¶
Field allowlisting, classification-aware redaction, and minimization.
Threat-model §6.2 classifies every element of a recording. TM-29b says the leak happens when scrubbing runs late or partially, so this module is allowlist-first: a field that nobody classified is dropped, not kept. Secrets and security tokens cannot be allowlisted at all — the allowlist refuses to construct — because "we allowlisted the pause token by mistake" must be a configuration error, not an incident.
Credential-shaped keys are dropped even when allowlisted, using the framework's single credential-key pattern, so SEC-CRED-2 holds at capture time regardless of what the allowlist says.
Allowlist rows are leaf rows. A row naming a container never keeps the container wholesale — the walk descends anyway and each leaf inside needs its own row — because a container row would otherwise carry an unclassified, credential-bearing subtree straight into the recording.
AllowedField
dataclass
¶
AllowedField(path: str, data_class: DataClass, redaction: Redaction = Redaction.NONE, keep_chars: int = 64)
One dotted path that may be captured, and how it must be transformed.
DataClass ¶
Bases: StrEnum
Threat-model §6.2 classes, in ascending order of "must not capture".
FieldAllowlist ¶
Allowlist-first projection of a payload.
Source code in src/symfonic/services/shadow/redaction.py
PayloadMinimizer
dataclass
¶
Payload minimization: cap string length and collection width.
scrub_credential_keys ¶
Drop credential-shaped keys from any nested structure, at every depth.
The allowlist walk covers mappings, but an allowlisted leaf can still be a list of mappings (a tool result, a message array), and a recorded port answer is not walked by the allowlist at all. SEC-CRED-2 has to hold for those too, so this is the one credential check both paths call.