A trust boundary is where data arrives from someone you do not control: an HTTP request, a queue, a file, a third-party API response, model output (48). There is one rule: at the boundary, input is parsed into a type rather than checked along the way — a check throws away the knowledge that it passed (2), and whoever comes next in the code has to check again. A type keeps that knowledge, and the compiler will not let you forget it.
“Trust boundary” is a threat-modelling term: in a data-flow diagram it is the line where the level of privilege changes, and Adam Shostack’s Threat Modeling: Designing for Security (2014) makes it the place where threats are enumerated (STRIDE). The second half of the rule — parse at the boundary into a type — is Alexis King’s “Parse, don’t validate” (2019), and an older relative is Perl’s taint mode, which marks external data and refuses to use it in system calls until it has been explicitly extracted.
// The type records where the data came from
pub struct Untrusted<T>(T); // raw, came from outside
pub struct Sanitized<T>(T); // went through the parser
impl Untrusted<String> {
pub fn parse_html_fragment(self) -> Result<Sanitized<String>, Rejected> { ... }
}
// render(s: Sanitized<String>) physically cannot accept raw inputSanitization is contextual. A single Sanitized<String> type creates a false sense of universal safety: what is safe as HTML text is dangerous in an attribute, in a URL, in an SQL identifier and in a shell argument. Types must name the sink — SafeHtmlText, SqlIdentifier, ShellArg, SafeUrl — and escaping must happen at the boundary of the specific sink.
Where the boundary actually is. Usually further in than people draw it. An internal service that trusts an X-User-Id header because “only the gateway calls us” has a boundary at its own port, enforced by nothing; a Kafka topic another team writes to is a boundary; so is a file in a shared bucket. Deserialization is the classic failure: Java native serialization or Python pickle lets the input choose which types to construct, so parsing must go through a format where the code, not the data, picks the type. The opposite mistake — re-validating everywhere inside one process — is not extra safety but the symptom the rule removes: parse once, then trust the type, as long as the network between components is itself authenticated (102).