Skip to content

ADR 0019: Untrusted mail input and a policy gateway that AI cannot bypass

Status

Accepted

Context

Duckbill™ ingests HTML, URLs, and attachments from untrusted senders. Model output will recommend actions, but 01 AI §8 and the Sprint 05 goal require that AI never call provider adapters. Pipeline §§114–118 require sanitizer failure to be plain text and scanner failure to hold rather than mark CLEAN. Complex rules-builder UI is out of MVP (04 MVP §3).

Decision

  1. Sanitize all HTML through packages/application/src/html-sanitize.ts. Fail closed to plain text. Do not render raw provider HTML.
  2. Inspect every outbound fetch URL in ssrf-guard.ts. Deny private, loopback, link-local, and metadata destinations. On scanner failure set unknown/held, never clean. Quarantined items do not auto-archive. Extend Stream 04 scan_state (unknown/held) rather than replace it.
  3. Put evaluatePolicy in front of send, delete, archive, and label. AI actors are denied those side effects. The gateway never invokes adapters. Denied results use POLICY_DENIED.
  4. Ship conservative deterministic rules (sender/subject/List-Id) with list/create/pause only. Suggested rules cannot start ACTIVE. Security and user rules beat AI by skipping the model. Rules emit command requests; they do not call adapters.

Alternatives

Consequences

Application services own sanitizer, SSRF, policy, and rules. Stream B (classification, extraction, drafts) remains separate and still cannot call adapters. Attachment hold states from migration 0014 stay valid. Dangerous feature flags remain default off (ADR 0003).