Never Trust Content
Last reviewed
AdvancedWhat you'll learn
~25 min- Apply Zero Trust tenets to the files that cross your boundary - rebuild, never filter
- Design structural fail-closed behavior: six independent closure points, no procedural safety
- Carry provenance forward: sanitized content stays labeled untrusted, feeding downstream defenses
The boundary nobody models
Meridian receives files all day: vendor invoices, regulator spreadsheets, contractor CAD exports, resident complaints with photo attachments. Every one crosses the trust boundary and lands in mailboxes, ticket systems, and — increasingly — AI assistants that parse and act on them. The identity pillar authenticated the sender’s mailbox; nothing authenticated the bytes. Malicious content inside legitimately-delivered files is the classic gap, and 2026 added a twist: prompt-injection payloads aimed at the agents from Lesson 2.4.
This lesson builds the Zero Trust answer — content disarm and reconstruction (CDR) — through a real, working pipeline (~750 lines of Python you’ll assemble with your AI CLI). Its design philosophy in one line: don’t inspect files for badness; rebuild them from scratch and let badness fail to survive the rebuild.
The architecture: dirty → wash → trusted or quarantine
DIRTY zone WASH (the conveyor) TRUSTED zone untrusted ──► 1. size-gate BEFORE reading ──► rebuilt artifact files land 2. true-type by magic bytes + manifest (extension = unverified claim) 3. route to allowlisted engine ──► QUARANTINE (7 admitted types, no more) everything else, 4. engine PARSES and RE-EMITS sealed + owned a brand-new artifactEach engine never “cleans” the input — it extracts the meaning and emits a new file: JSON re-serialized field by field, CSV cells re-escaped, HTML reduced to inert text plus a labeled link inventory, images decoded to pixels and re-encoded from pixels alone. The elegance shows in what doesn’t happen: a polyglot file (valid PNG with an executable payload appended) is never “detected” — the engine decodes pixels, re-encodes pixels, and the payload simply isn’t in the output, because only pixels survive reconstruction. Detection can lose an arms race; reconstruction doesn’t enter it.
Fail-closed as structure, not procedure
The pipeline’s safety is not a code path that handles errors — it’s the shape of the data. The manifest is born quarantined:
@classmethoddef start(cls, name: str, data: bytes, true_type: str) -> "Manifest": return cls( object_id=str(uuid.uuid4()), original_name=sanitize_name(name), sha256_original=sha256(data), size_original=len(data), true_type=true_type, verdict="quarantine", # fail-closed default until an engine passes it processed_at=datetime.now(timezone.utc).isoformat(timespec="seconds"), )Six independent closure points guard the trusted zone: oversize files are gated before being read into memory; a type with no engine quarantines; an engine that raises any exception quarantines (bare except Exception is normally a smell — here it is the policy, because a crash on hostile input is hostile input):
try: result: EngineResult = engine(data)except Exception as e: # engine crash = hostile input until proven otherwise m.findings.append(f"engine error ({type(e).__name__}): {e}") store.write_quarantine(m, data) store.append_audit(m) return m…a PASS verdict that produced no artifact still quarantines; the manifest’s default only mutates on explicit success; and the trusted-zone write has exactly one call site, behind a compound gate — note the artifact name is derived from a generated UUID, never from attacker-controlled input, making path traversal impossible by construction:
if result.verdict is Verdict.PASS and result.artifact is not None: from .manifest import sha256 m.verdict = "pass" m.artifact_name = f"{m.object_id}{result.artifact_ext or '.bin'}" m.sha256_artifact = sha256(result.artifact) m.size_artifact = len(result.artifact) store.write_trusted(m, result.artifact)else: store.write_quarantine(m, data)The governance consequence deserves its own sentence: because an unhandled type simply quarantines, every deferral is a throughput decision, never a safety decision — which is what makes shipping a security control incrementally legitimate.
Two more shapes worth stealing. The intake iterator treats the drop directory as attacker-controlled namespace, not just attacker-controlled bytes:
def iter_landing(self): # skip symlinks — a drop containing a symlink must not let the conveyor # read arbitrary host files yield from sorted( p for p in self.landing.iterdir() if p.is_file() and not p.is_symlink() )And the URL defanger teaches three rules in ten lines — decide on the normalized copy but act on the original; allowlist safe schemes rather than blocklist dangerous ones; defang, don’t delete, so the analyst keeps the evidence while the payload can’t re-activate:
def neutralize_scheme(url: str) -> str: stripped = url.strip().replace("\x00", "") # detect on a percent-decoded, whitespace-free copy so %6Aavascript: and # "java\tscript:" can't smuggle a live scheme past the check probe = re.sub(r"\s+", "", unquote(stripped)) m = _SCHEME_RE.match(probe) if m and m.group(1).lower() not in _SAFE_SCHEMES: # defang: break the scheme's colon so the token can never re-activate -- # including an ENCODED colon (%3A), or the percent-decoded form # re-arms the moment a downstream consumer decodes it defanged = stripped.replace(":", "[:]", 1) defanged = re.sub(r"%3a", "[%3a]", defanged, count=1, flags=re.IGNORECASE) return "[defanged]" + defanged return stripped
_SAFE_SCHEMES = {"http", "https", "mailto", "tel"}_SCHEME_RE = re.compile(r"^([a-z][a-z0-9+.\-]*):", re.IGNORECASE)Tenets, made concrete
- Never trust content: the extension is a claim, the binary signature decides — and the output stays labeled untrusted even after sanitization. The provenance flag follows the artifact into mailboxes and AI context windows, feeding downstream prompt-injection defenses. Continuous distrust, not one-time clearance.
- Verify explicitly: dual SHA-256 hashes — the original at intake, the rebuilt artifact after — so quarantined evidence and released file are independently addressable and provably linked. Every transform lands in the manifest; every object appends exactly one line to an append-only audit log (opened in append mode, one JSON object per line, no read-modify-write path anywhere). Honest caveat to carry into your design review: hashes-in-a-log is not a tamper-evident ledger — this design pushes tamper-evidence to immutable log storage rather than solving it in-process.
- Assume breach — including of the sanitizer: the engine’s own decoder parses hostile bytes; the compensating control is running each wash in an ephemeral, per-file container. And the pipeline’s one-way property is enforced by identity, not code order: the ingest identity can write only to dirty, the wash identity reads dirty and writes trusted/quarantine — a fully compromised ingest still cannot touch the trusted side. (Lesson 3.4’s data diode, reborn as IAM.)
Your artifact: build it, then attack it
Build the CDR pipeline as specified above (stdlib + an imaging library only),then write its adversarial test suite across these categories: disallowedtrue-types must quarantine; type-confusion in BOTH directions; maliciouscontent inside ADMITTED types must be neutralized while the file passes(stored-XSS in JSON, dangerous URI schemes, spreadsheet formula injection);FIDELITY - clean files must round-trip unchanged (this forbids the degeneratequarantine-everything solution); resource-exhaustion bounds; and allowlistcorrectness in both polarities. Include the polyglot test (payload appendedto a valid image - assert the payload is absent from the rebuilt file) and adecompression bomb sized between OUR pixel cap and the imaging library'sbuilt-in threshold - your policy limit and the library's default aredifferent numbers, and the gap between them is exploitable.That last test is the suite’s sharpest lesson, and the fidelity category is its conscience: a sanitizer that quarantines everything passes every security test and fails its purpose.
A teammate proposes simplifying: 'Our upstream email-security vendor already scans and sanitizes attachments — files arriving from it are pre-cleaned, so route them straight to trusted and save the compute.' What does this lesson's architecture say?
Key takeaway
Rebuild, never filter: seven admitted types, engines that re-emit from meaning, six structural closure points guarding one trusted-write call site, dual hashes and an append-only audit as the evidence chain, and provenance that keeps the file honest downstream. Detection argues with attackers; reconstruction ignores them. Next: what the legitimate data needs from policy.