Skip to content

ADR-005: A single canonical sanitize injection boundary

Date: 2026-06-28 Status: Accepted Source: RD-08 (essentials gate, logging, errors & security)

Context

Untrusted text — user input, file contents, network data an app chooses to display — can contain control bytes and escape sequences. If such bytes reach the terminal as live cells, they can move the cursor, change modes, or inject sequences (a terminal injection attack). The engine needs a guaranteed point where this can never happen.

Options Considered

Option A: Sanitize at each draw call site, ad-hoc

  • Pros: Localized.
  • Cons: Easy to forget one path; inconsistent rules; no single guarantee.

Option B: One canonical sanitize boundary that all text crosses

  • Pros: A single, audited choke point; ScreenBuffer.text (and any text→cell path) routes through it, so control bytes never become cells; one place to reason about and test the rules.
  • Cons: Must ensure every text→cell path actually goes through it.

Decision

Chosen option: Option B — sanitize (exported from safety/) is the single canonical injection boundary; all untrusted text becomes cells only after passing through it.

Rationale

Concentrating the rule in one function makes the security property auditable and testable in isolation: control bytes are stripped before they can be serialized into escape sequences (RD-04 AC-8). Because ScreenBuffer.text sanitizes its input, an app cannot accidentally bypass the boundary by drawing untrusted strings. Capability responses are handled with the same untrusted-data posture (bounded, parsed as data only).

Consequences

Positive

  • A single, audited injection choke point; testable in isolation.
  • Apps get injection safety by default when drawing text.

Negative

  • Every new text→cell path must be routed through sanitize (enforced by review).

Risks

  • A future direct cell-write API could bypass the boundary — such APIs must document that they take pre-sanitized input or sanitize internally.