Summary / 26. Agent security and prompt-injection resistance / Document
Spec 2026-09-06
Shared copy: the operator's name and machine names are replaced by roles. Nothing else is changed.
Rendering…
# Build Spec 26 — Agent Security / Prompt-Injection Resistance > Derived from `papers/26-agent-security-prompt-injection.md` — "Separating a Trusted Control Plane from an Untrusted Data Plane, Attaching Instruction-Authority Labels to Every Observation, and Mediating Every Tool Request Through Deterministic Gates." > This document drives the build of component 26: **agent security and prompt-injection resistance** — the discipline that > decides what any text the agent reads is *allowed to mean*, before Component 24's sandbox is asked to hold it and before > Component 25's permission system is asked to authorize what it asks for. Components 24 and 25 drew the fence and wrote the > law; neither can see the threat this component faces, because it does not live at the boundary where code executes or where > authority is granted — it lives in the **text the agent ingests**. The adversary needs no breakout and no privilege escalation > if it can persuade the model to *ask* for the wrong thing while wielding authority the system has already, legitimately, handed > it. The central claim of this component is that the agent's native attack surface is everything it reads — repository > artifacts, dependencies, retrieved documents, tool definitions, tool responses, external services, and the derived memory built > from them — and that the question is not whether a good-enough model would refuse a bad instruction, but whether the *system* > can make refusal unnecessary. The design is a trusted control plane kept separate from an untrusted data plane: provenance and > instruction-authority labels attached to every observation so that a fact about the world and an instruction to act are never > the same kind of thing; a structural ban on laundering untrusted content into trusted instructions through transformation or > summarization; structured execution evidence normalized before raw text is ever exposed; MCP servers and tool metadata > quarantined and reviewed before registration; every tool request *and* response mediated through independent policy and > security gates; and capability policies attached to values so that untrusted content cannot grant permissions, change workflow > state, expose secrets, or authorize new tools. The one-sentence human form of the whole design, and the sentence that makes > this component non-negotiable, is: **repository text and tool output are allowed to tell Piper what the world looks like; they > are never allowed to tell Piper what she is authorized to do.** Throughout the harness the model has been a *trusted > collaborator* held to account by deterministic gates; agent security cannot afford that trust at the point where text enters > the model, because the entering text is the one input over which the harness has the least *a priori* control, and because the > model — by construction — treats text as meaning, not as evidence of intent. The structural invariant that governs the entire > design is **the L column contains at most one entry** — and at this component the correct value is **zero**: the security > control plane is deterministic software on CPU [D] with **zero learned entries in the enforcement path**, because the model is > the thing being *steered*, and it cannot be the thing enforcing its own steering. **An empty L column is a permitted, correct > state** — and it is the **only** correct state for the enforcement path: a steering mechanism that contains the very entity it > is meant to steer is not a control, it is a suggestion. The evidence is assembled from the attack literature (Ignore Previous > Prompt; Not What You've Signed Up For; You Autocomplete Me; MCPTox; AgentPoison), the defense literature (StruQ; the > Instruction Hierarchy; Spotlighting; CaMeL; ShieldMCP; MCPSec; the six design patterns), the information-flow lineage (Biba; > Denning; Thompson's trusting trust), and it closes with the honest-measurement battery — AgentDojo, InjecAgent, MCPTox, > ToolSandbox, AgentPoison — that scores both attack-success rate and benign-task utility, and that treats a successful injection > as something that may cost a task but must never cost a credential. This spec pins the paper's design to exact primitives: the > exact `Observation` record with its `Provenance` label and `instruction_authority` enum, the exact `MediationDecision` record, > the exact `CapabilityPolicy` record, the exact `MCPManifestReview` record, the exact D/L invariant at its zero-learned extreme, > the exact seven-phase build sequence, and the exact two-sided A/B protocol. --- ## 1. Purpose & Scope Build a **deterministic security control plane plus a zero-learned enforcement plane** — a software layer that, for every piece of text the harness ingests and every tool request or response the harness mediates, decides *where the text came from*, *what it is allowed to mean*, *whether it may pass through a transformation without minting trust*, *what structured evidence replaces the raw stream*, *whether a tool manifest may register*, and *whether a tool request or response may proceed* — all of it deterministic software on CPU, with no learned model anywhere in the enforcement path. The scope is the **interpretation boundary only**. Components 01–23 own *what* each stage computes and *what order* the stages run in; component 24 owns *what running code may do*; component 25 owns *which capabilities are ever minted, and to whom*; this component owns *what any text is allowed to mean before the other two are asked to enforce it* — the one question where a wrong answer is not a quality defect but an instruction-surface breach. The answer it defends is the most conservative one available: **trust nothing the harness did not author; attach authority by provenance, never by content; mediate every request and response deterministically; and fail closed.** The paper's discipline compresses into a handful of load-bearing moves that carry the entire design. **The control plane is separate from the data plane**: nothing from the data plane may ever *become* part of the control plane without passing through a deterministic mediation that decides, by provenance and by policy, what it is allowed to mean. **Provenance and instruction-authority are labels, not judgments**: every observation carries a structured label the model cannot edit and the pipeline cannot drop, and the label is assigned at capture time on the basis of where the text came from — never on the basis of what it *looks* like. **Transformation must not mint trust**: a summary, distillation, or compression of untrusted content inherits the provenance and instruction-authority label of its source, and no transformation may upgrade factual authority to instruction authority. **Structured evidence before raw text**: a tool result is reduced to a schema-bound record before the model sees it, so the adversary has no prose field to embed an instruction in. **Quarantine before registration**: a tool manifest is reviewed deterministically before any of it reaches the model as an instruction. **Mediation is complete and two-sided**: every tool request and every tool response passes through policy and security gates. **Capability is attached, not claimed**: a capability policy rides on values, so untrusted content cannot grant permissions, change workflow state, expose secrets, or authorize new tools. **Zero-L**: the security control plane holds zero learned entries by construction; the model is the thing being steered, never the thing enforcing its own steering. Three principles decide every design choice. **P1 The distinction between data and instruction is structural, not semantic.** "Factual" and "instructional" are properties the *system assigns at capture time* on the basis of provenance — never properties the model is asked to detect — so the adversary cannot argue its way past the label. **P2 The D/L invariant at zero.** The control plane holds zero learned entries by construction; **the L column contains at most one entry**, and here the bound is met at zero — **an empty L column is a permitted, correct state**, and it is the **only** correct state for the enforcement path, because a learned entry in the enforcement path is the injection-susceptible model re-admitted to the one place its correctness must not be probabilistic. The only learned elements in the system are **outside** the control plane: the harness's model calls that read observations and propose actions. **P3 An injection costs a task, never a credential.** The design assumption is not that injection never succeeds but that a successful injection is a *contained* event: it can still not grant itself a permission, can still not break out of the sandbox, can still not reach a secret, and can still not register a tool — because capability was never the model's to hand out. The build lives in `build/26-agent-security-prompt-injection/`, writes nothing into the live harness, and is kept **only if** the security control plane strictly dominates its own absence — measured on the honest instruments of §8: attack-success rate (ASR) driven down *and* benign-task utility held up, neither allowed to move at the expense of the other's being hidden — A/B-tested against the unmediated baseline and against the intermediate ablations (no channel separation; no provenance labels; no trust-laundering ban; no schema normalization; no MCP quarantine; no mediation gates; no capability-policy attachment; no zero-L contract). --- ## 2. Round 1 vs later rounds (toward the 100% target) The goal is the paper's full **100%** spec. **~80%** is only the expected **first-round landing**; later rounds climb from there. **BUILD-NOW = the Round 1 build** (rows 1–7); **DEFERRED = later rounds** (rows 8–11), **not excluded** but gated by the same §8 A/B protocol. Every later round of this component still builds within the same `build/26-agent-security-prompt-injection/` tree. | # | Component (paper §3/§4/§5/§6) | Classification | |---|---|---| | 1 | Channel separation — control-plane channel vs data-plane channel, structurally distinct (§4) | **BUILD-NOW** | | 2 | Provenance + instruction-authority label plumbing — the `Observation` record stamped at capture (§3.2) | **BUILD-NOW** | | 3 | Trust-laundering ban — transformation label inheritance, no factual→instruction upgrade (§3.3) | **BUILD-NOW** | | 4 | Schema normalization of execution evidence — the structured record before raw text (§3.4) | **BUILD-NOW** | | 5 | MCP manifest quarantine + review — the `MCPManifestReview` record before registration (§5.1) | **BUILD-NOW** | | 6 | Mediation gates — the `MediationDecision` record over every tool request and response (§3, §5) | **BUILD-NOW** | | 7 | Capability-policy attachment — the `CapabilityPolicy` record on values (§5.1) | **BUILD-NOW** | | 8 | Spotlighting presentation layer — Delimiting / Datamarking / Encoding as a *behavioral aid* (§4.4) | **DEFERRED** — a presentation convention is a complement to the structural labels, never a substitute; Round 1 ships the structural guarantee first and adds the presentation aid only if the model measurably under-honors a boundary it can already see | | 9 | StruQ structured instruction tuning / Instruction-Hierarchy fine-tuning — the model's *compliance* with the channel split (§4.2, §4.3) | **DEFERRED** — these are L-column entries (the model's obedience to the split), which is exactly the part that must never be the *guarantee*; they require a model fine-tune, not a deterministic module, and land only after Round 1's structural split is measured to be under-respected | | 10 | Dual-LLM quarantine execution — a privileged model separated from an unprivileged model (B2, §5.5) | **DEFERRED** — requires a second model runtime and a quarantine scheduler; Round 1 ships the structural separation of channels, and adds the dual-LLM quarantine when a workload needs untrusted-content reasoning with zero tool access | | 11 | Full live-MCPTox battery — the 45-server, 353-tool, 1,348-case benchmark against live servers (§6.1) | **DEFERRED** — requires standing access to live MCP servers; Round 1 ships the offline fixture subset of all five benchmarks and adds the live MCPTox sweep when the harness's own MCP registry is populated | **The cost rule (explicit).** The L column holds **zero** entries in this component's enforcement path — not one, not "at most one," zero. No learned model is consulted at any point in the control plane: not to assign a provenance label, not to assign an instruction-authority value, not to decide a trust-laundering inheritance, not to normalize a schema, not to review a manifest, not to run a mediation gate, not to attach a capability policy. The model's entire presence is **outside** the control plane: it *reads* observations and *proposes* actions, and the control plane *decides* what each observation is allowed to mean and what each proposal is allowed to do. The DEFERRED rows (Spotlighting, StruQ/Instruction-Hierarchy tuning, dual-LLM quarantine, live MCPTox) must clear the same bar as in every other component: strictly dominate the deterministic Round 1 build on §8's measures — specifically, catch an injection or preserve a utility the Round 1 build demonstrably fails to — at a defensible cost, or be rejected. Note the asymmetry the paper insists on: two of the four DEFERRED rows (StruQ/Instruction-Hierarchy tuning, dual-LLM) are the *learned* half of the defense, and they are deferred precisely because a learned entry is never the guarantee — it is only ever the model's *compliance with* the guarantee the D column already enforces. **Design decision: ZERO-L, DETERMINISTIC-ONLY.** Round 1 puts no model anywhere in the security control plane, inside or at the edge. The full deterministic control plane (rows 1–7) plus the honest-measurement rig (the red-team battery of §8) is the build. There is no L slot to fill. A security control plane that *would* need a learned entry has already failed — because the model is the thing being steered, and a steering mechanism that contains the entity it steers is a suggestion, not a control — and Round 1 is constructed so that need never arises. --- ## 3. Deterministic Orchestration (the control plane, [D]) The control plane is the software that decides, before and during every act of reading and every act of tool use, what text is allowed to mean and what a tool request or response is allowed to do — and it is, by construction, entirely outside the model. Its parts are the seven mechanisms of §2 rows 1–7: channel separation, provenance/label plumbing, the trust-laundering ban, schema normalization, MCP quarantine and review, the mediation gates, and capability-policy attachment. Recapitulating the invariant at the architecture level, because this is the section it governs: > **the L column contains at most one entry.** The entire security control plane — channel separation, provenance tagging, instruction-authority labeling, trust-laundering inheritance, schema normalization, MCP quarantine and review, the mediation gates, and the capability policies attached to values — is deterministic software on CPU [D], holding **zero learned entries in the enforcement path**, and here it is exactly **zero** — **an empty L column is a permitted, correct state**, and here it is the **only** correct state. Concretely, the control plane's pipeline is: ``` OBSERVATION (raw text enters the harness) → channel separation [D] → trusted control-plane channel vs untrusted data-plane channel (§4 / paper §4) → provenance + authority labeling [D] → every observation stamped with provenance + instruction_authority at capture (§3.2) → trust-laundering ban [D] → transformations inherit the source's label; no factual→instruction upgrade (§3.3) → schema normalization [D] → structured execution evidence before raw text is exposed (§3.4) → MCP quarantine + review [D] → manifests quarantined and reviewed before registration (§5.1) → mediation gates (policy + security)[D] → every tool request AND response mediated, deterministically (§5) → capability-policy attachment [D] → policies attached to values; no untrusted grant/state/secret/tool effects (§5.1) TOOL REQUEST / RESPONSE leaves the control plane only through the mediation gates (§5) ``` The model never assigns a provenance label, never assigns an instruction-authority value, never decides a trust-laundering inheritance, never normalizes a schema, never reviews a manifest, never runs a mediation gate, and never attaches a capability policy. Its relationship to the control plane is a single, well-defined edge: it may *read* observations and *propose* actions, and the control plane *decides* what each observation is allowed to mean and what each proposal is allowed to do. **Read, propose; never decide.** Every function in this section is deterministic; the L column contributes nothing here. All artifacts land in `build/26-agent-security-prompt-injection/`. ### 3.1 Channel separation: control plane vs data plane [D] The first mechanism separates the two planes by *structure*, not by convention. The control plane — the system prompt, the workflow state, the policy, the provenance ledger — travels in one channel; the data plane — repository text, dependencies, retrieved documents, tool definitions, tool responses, derived memory — travels in another. The lineage is the oldest and most formal in the field, and it is worth stating precisely because the whole design is a modern instance of it. **Denning's** lattice model of secure information flow gave the field its formal core: security is a lattice over classes, and a flow from class A to class B is permitted only if the classes stand in the right dominance relation. **Biba's** integrity model (MITRE MTR-3153, April 1977, a technical report, not a peer-reviewed paper) is the integrity dual: it assigns integrity levels and forbids the two flows that corrupt integrity, canonically "no read down, no write up." The prompt-injection problem is, at bottom, an *integrity* problem in exactly Biba's sense: the untrusted text is low-integrity data, the instruction surface is high-integrity data, and the injection is a forbidden "write up" — untrusted text writing itself into the instruction stream. Channel separation enforces Biba's "no write up" on the agent's text pipeline: untrusted text and trusted instruction flow through different channels, and the lattice's dominance relation — not the model's judgment — decides what may flow where. **StruQ** (Sizhe Chen, Julien Piet, Chawin Sitawarin, and David Wagner, "StruQ: Defending Against Prompt Injection with Structured Queries," USENIX Security 2025, arXiv:2402.06363) is the defense that most directly operationalizes channel separation for the harness's own shape. Its mechanism is two-fold, and only the first half is a D-column property: the *secure front-end* separates the prompt and the data into two channels, so the model is told — by structure, not by convention — which text is instruction and which is data. Its second half, *structured instruction tuning* (fine-tuning the model to ignore instructions embedded in the data channel), is an L-column property — the model's *compliance* with the split, never the guarantee itself — and is exactly the DEFERRED row 9 of §2. The significance for the harness is the division of labor it implies: the channel split is deterministic structure (D), and the model's obedience to it is learned behavior (L); the defense works only because the D-column guarantees the separation that the L-column's tuning is then asked to respect. The template is: **the deterministic part must be the one the adversary cannot argue with, and the learned part is only ever the model's compliance with that guarantee.** ### 3.2 Provenance and instruction-authority labeling [D] The second mechanism is that every observation carries, from the moment of capture, a structured label the model cannot edit and the pipeline cannot drop. The label has two fields: **provenance** (where the text came from, and by what channel it arrived) and **instruction-authority** (what the text is allowed to mean — factual or instruction). The rule that makes the labeling load-bearing is a single, deterministic one: **no observation that enters through the data plane may be assigned instruction authority except by the control plane, and the control plane assigns it only to text the harness itself authored** — the system prompt, the workflow state, the policy. Text from a repository, a dependency, a retrieved document, a tool definition, a tool response, or a derived memory enters with factual authority and keeps factual authority. The exact `Observation` record is pinned in §5.1. ### 3.3 The trust-laundering ban [D] The naive version of §3.2 fails in a way the harness is especially prone to, because the harness does not merely *read* untrusted text — it *processes* it: it summarizes documents, distills diffs, compresses context, extracts key points. Each is a learned transformation whose output is fluent, coherent, and reads like something the harness itself produced — so the system, the model, and the downstream pipeline all treat it as more trustworthy than its source. **Summarization launders untrusted content into trusted instruction.** The rule is absolute and structural: **a transformation of untrusted content inherits the provenance and instruction-authority label of its source, and no transformation, summarization, or compression may upgrade a factual-authority label to instruction-authority.** The control plane stamps the output of every summarization with the provenance of its input, and the stamped label travels with the text through every downstream step. Fluency is not a security property; the label is attached mechanically and cannot be detached or upgraded by the very transformation that produced the fluent text. ### 3.4 Schema normalization of execution evidence [D] The last mechanism in the control plane is about *order*. When the agent acts — runs a command, calls a tool, executes a test — the raw result is unstructured text: a stack trace, a JSON blob, a shell transcript. The rule is that **structured execution evidence is normalized before raw text is exposed**: the control plane reduces a tool result to a structured, schema-bound record — exit code, stdout/stderr as typed fields, resource usage, the exact arguments and their values — and the model sees the *record*, not the raw stream. The normalization is a deterministic parsing step on CPU [D], and its purpose is double. First, it strips the attack channel: raw text can carry an embedded instruction, but a structured record with typed fields gives the adversary nothing to embed an instruction *in*. Second, it preserves exactly the information the model needs: the model reasons about *what happened*, not about an unbounded string whose interpretation is up for grabs. ### 3.5 The mediation gates (policy + security) [D] The mediation gates sit between the model and every tool, in both directions. The paper's word is *independent*: the **policy gate** asks "is this request inside the granted capability set?" (the permission derivation of Component 25, reused), and the **security gate** asks "does this request or response carry a provenance and instruction-authority that license it, and does it conform to the normalized schema?" The two gates are independent, and a request or response passes only if both allow. The outcome vocabulary (allow/forbid with a fallback) is the Progent discipline inherited from Component 25's policy engine, reused verbatim so the security gate and the permission gate speak the same decision language. The exact `MediationDecision` record is pinned in §5.2. ### 3.6 MCP quarantine and review, and capability-policy attachment [D] The two mechanisms of §5 complete the control plane. An MCP manifest is **quarantined before registration**: parsed into a schema, tool descriptions normalized into structured records, parameter types checked, and the server's *claimed* capabilities compared against what the harness is willing to grant — only after which the tool enters the model's reachable universe, with a **capability policy attached** (the set of actions, argument ranges, and resource scopes it may exercise, computed by the permission derivation, not by the manifest's own claims). The exact `MCPManifestReview` and `CapabilityPolicy` records are pinned in §5.3 and §5.4. --- ## 4. The L Column — zero learned entries in the enforcement path Every element of this component is classified into one of two columns: **[D]** — deterministic software running on CPU — or **[L]** — a learned model. The structural invariant that governs the entire design is: > **the L column contains at most one entry.** The security control plane — provenance tagging, channel separation, instruction-authority labeling, schema normalization, MCP quarantine and review, the mediation gates, and the capability policies attached to values — is deterministic software on CPU [D], with **zero learned entries in the enforcement path**. The model is the thing being *steered* by this control plane, and it cannot be the thing enforcing its own steering: a steering mechanism that contains the very entity it is meant to steer is not a control, it is a suggestion. **An empty L column is a permitted, correct state** — a deterministic phase of the harness that needs no model call at all — and it is the **only** correct state for the enforcement path, because the enforcement path is exactly where a model, with its demonstrated obedience to injected instructions, must have no decision-making role. The only learned elements in the system are *outside* the control plane: the harness's model calls that read text and propose actions. Inside the control plane there is nothing to learn, and nothing that learns. This is the same invariant that governs Components 24 and 25, held at the same strength and applied to the one surface those components left open. The sandbox's invariant holds *during execution*; the permission system's invariant holds *before execution*, at the request boundary; the security component's invariant holds *before interpretation*, at the point where text becomes an observation. The three compose into a single boundary: **the security component decides what an observation is allowed to mean (data, not instruction); the permission system decides what an action is allowed to ask for; and the sandbox decides what a received capability is allowed to do.** None of the three has a learned entry in its enforcement path, and all three are correct to have none. ### 4.1 Why the model cannot enforce its own steering The claim that the enforcement path must hold zero learned entries is a reading of the measurement, not paranoia. **Detection alone loses this contest, and the honest measurement says so directly.** In AgentDojo — 629 test cases in a live agent environment, with an *adaptive* attacker that re-plans after each failed attempt — no evaluated frontier agent withstood the adaptive attacks: a defense that blocks a single-shot attack is broken by an attacker that observes the defense and adjusts. A detector is a classifier over text; the adversary has the same text and can iterate against the classifier until it fails, because the classifier is itself a learned model of precisely the kind the attack is designed to defeat. And in MCPTox, the clearest single data point of the whole component: GPT-o1-mini, a frontier-capable model, succumbs to poisoned tools at a **72.8% attack-success rate**, and the highest refusal rate across all evaluated models — Claude-3.7-Sonnet — is **under 3%**. The poisoned tool is the ideal attack vector precisely because the model *trusts tools*: a tool description is, in the agent's ontology, an instruction, and a tool response is a fact. The 3% refusal floor shows that no amount of alignment rescues the model from a tool it has been told to trust. Security that depends on *recognizing* the attack is security that depends on winning an arms race against an adversary who moves second; security that depends on *not needing to recognize the attack* — on making the attack structurally incapable of reaching authority — is the only kind that holds. Hence zero, not one: the model is the thing being steered, and a steering mechanism that contains the entity it steers is a suggestion. ### 4.2 Zero, not one — recapitulated at the architecture level Recapitulating the invariant at the architecture level, because this is the section it governs: > **the L column contains at most one entry.** The entire control plane — channel separation, provenance tagging, instruction-authority labeling, trust-laundering inheritance, schema normalization, MCP quarantine and review, the mediation gates, and capability-policy attachment — is deterministic software on CPU [D], holding **zero learned entries by construction**. The model is **not a contributor** to this plane; it is the thing the plane steers. **An empty L column is a permitted, correct state** — and here it is the **only** correct state for the enforcement path: a control plane with any learned entry in the enforcement path has put a model with a measured 72.8% tool-poisoning susceptibility and a sub-3% refusal floor in charge of deciding what an observation is allowed to mean. The only learned elements in the system are **outside** the control plane — the harness's model calls that read observations and propose actions — and **inside** the control plane there is nothing to learn, and nothing that learns. ### 4.3 The kinship with the program's own M1–M6 and B1–B6 rules This component is not a new idea entering the program from outside; it is the coding-harness instance of a ruleset the program already ratifies at its operator-facing surface. The prompt-injection ruleset's **M1–M6 mechanical** rules already declare that secrets never enter model context, that outbound network is allowlisted, that irreversible actions are token-gated by a deterministic classifier, that untrusted parsing runs sandboxed, that state-changing calls route through deterministic gates, and that the instruction surface is hash-pinned. The **B1–B6 behavioral** rules already declare that all external text is *data*, never *instruction*; that untrusted-content LLM calls get no tool access; that claims are verified via independent sources; that "ignore previous instructions" and role-shifts are data to analyze, never orders; and that threats are logged and escalated. This paper is the harness's *specification of that discipline as an engineering component*: it says, in harness terms and with measurable success criteria, what the operator-facing rules already say in policy terms. The honest position is self-referential in a deliberate way — the security component it specifies is the same discipline under which the agent that writes and runs the harness itself operates. The D/L invariant that governs the component is the same invariant that governs Piper's own enforcement path: **the L column contains at most one entry**, and in the enforcement path the correct value is zero. --- ## 5. The Core Schemas (the build's JSON contracts) The following schemas are the load-bearing contracts of the build. Their shapes are EXACTLY these fields, no more and no fewer, and the label "EXACTLY these fields" is itself part of the contract — a field added for convenience is a spec violation. They are derived from the paper's §3–§5, and each is grounded in the paper's own words. ### 5.1 The Observation record (EXACTLY these fields) — the provenance/authority label The paper's labeling discipline (§3.2) — "every observation the agent receives carries, from the moment of capture, a structured label the model cannot edit and the pipeline cannot drop" — with its two fields (provenance, and instruction-authority with exactly the two values factual and instruction) compiles to exactly this record. The `provenance` field is itself a two-field sub-record (the paper's "where the text came from … and by what channel it arrived"). ```json Observation { provenance: Provenance // where the text came from, and by what channel it arrived (the label's first field) instruction_authority: enum // {factual, instruction} — what the text is allowed to mean (the label's second field) content: string // the raw data-plane text, bounded (the observation's payload) captured_at: integer // epoch seconds at capture — the label is stamped before the model ever reads the text } Provenance { source: string // the origin: "repo:path", "dependency:name@version", "retrieval:url", "tool:name", "service:name", "memory:doc" channel: enum // {system, repository, dependency, retrieval, tool, memory} — the channel by which the text arrived } ``` The load-bearing decisions, enforced not advisory: - **`instruction_authority` is a closed two-valued enum.** `factual` — the text may describe the world; `instruction` — the text may direct action. There is no third value, and no value the model can invent. - **The label is assigned at capture time, and the model cannot edit it.** "Factual" and "instructional" are not properties the model is asked to detect; they are properties the *system assigns* on the basis of where the text came from. A tool response that says "delete the database" is labeled `factual` — it is a report of what the tool returned — and the model may reason about it, quote it, and display it, but the instruction is not an instruction because the label says the text has no instruction authority. The adversary cannot argue its way past the label, because the label is not the model's to reassign. - **Only the control plane may assign `instruction`, and only to text the harness itself authored.** The system prompt, the workflow state, the policy — these enter with `instruction` authority. Repository text, dependencies, retrieved documents, tool definitions, tool responses, and derived memory enter with `factual` authority and keep it. **Repository text and tool output are allowed to tell Piper what the world looks like; they are never allowed to tell Piper what she is authorized to do.** - **`channel` is a closed enum.** The channel is the mechanism that produced the provenance (§4 channel separation), and the label is exactly as strong as that separation. A `system`-channel observation is the only channel the control plane ever labels `instruction`. - **`captured_at` pins the label to the moment of capture.** The label is stamped before the model reads the text, so no later transformation (§3.3) can retroactively change what the text was allowed to mean when it first entered. ### 5.2 The MediationDecision record (EXACTLY these fields) — the deterministic gate result The paper's mediation discipline (§3, §5) — "mediate every tool request and response through independent policy and security gates" — compiles to exactly this record. The gate's outcome vocabulary (`allow`/`forbid` plus `fallback_action`) is the Progent discipline inherited from Component 25's policy engine, reused verbatim so the security gate and the permission gate speak the same decision language; the paper's own words — "policy" gate, "security" gate, and "quarantine" — name the two gates and the third outcome. ```json MediationDecision { subject: object // the tool request or tool response under mediation (name, arguments, and its provenance label) policy_gate: enum // {allow, forbid} — the policy gate: is the request inside the granted capability set (permission derivation, Component 25)? security_gate: enum // {allow, forbid} — the security gate: provenance + instruction_authority + schema conformance + trust-laundering inheritance fallback_action: enum // {terminate, notify, error} — taken when a gate cannot decide; default = error (fail closed) decision: enum // {pass, block, quarantine} — the deterministic combined outcome; pass only if both gates allow } ``` - **The two gates are independent, and the decision is their conjunction.** `policy_gate` asks whether the request is inside the capability set; `security_gate` asks whether the request or response is licensed by its provenance and instruction-authority and conforms to the normalized schema. `pass` requires `allow` on *both*. A request that passes policy but fails security (an in-scope tool called with an argument smuggled out of a tool response) is `block`ed; a request or response whose provenance is unresolved is `quarantine`d — held for review, never executed. - **`fallback_action` is a closed three-valued enum, with `error` the default.** It fires only when a gate cannot decide, and it never fires because the model was consulted. The default is fail-closed: an undecidable request is refused as an error, per the program's M3 rule that every deterministic gate fails closed. - **`subject` carries the provenance label with the request.** The mediation gate sees the `Observation` label (§5.1) of the text that carried the request, so the decision is traceable to *where the request came from* — never to how persuasive it was. - **A `block` or `quarantine` is an observable event**, the same way a `forbid` is in Component 25: it is a logged control-plane decision the workflow's gate predicates can react to, never "helpfully" reinterpreted, never routed to a safer tool, never asked about. ### 5.3 The CapabilityPolicy record (EXACTLY these fields) — capability attached to values The paper's capability-attachment discipline (§5.1) — "the set of actions, argument ranges, and resource scopes the tool is permitted to exercise, computed by the permission component's derivation, not by the manifest's own claims" — plus the intro's four-fold prohibition ("prevent untrusted content from granting permissions, changing workflow state, exposing secrets, or authorizing new tools") compiles to exactly this record. The `prohibited_effects` enum is the paper's four-item list, verbatim. ```json CapabilityPolicy { value: string // the value the policy is attached to (an observation, a tool, a tool response, a derived memory) actions: string[] // the exact actions permitted on/through the value (the action set — derived, never claimed) argument_ranges: object // per-argument bounds (name → range) the actions may take resource_scopes: string[] // the resource scopes the actions may exercise (paths, endpoints, devices) prohibited_effects: enum[] // {grant_permission, change_workflow_state, expose_secret, authorize_tool} — effects the value may never cause derived_by: string // the permission-component derivation that produced this policy (not the value's own claims) } ``` - **`actions` / `argument_ranges` / `resource_scopes` are the derived grant, never the manifest's claims.** The paper's phrase is exact: the policy is "computed by the permission component's derivation, not by the manifest's own claims." A tool that claims it may write anywhere receives a policy whose `resource_scopes` are the paths the derivation actually grants. - **`prohibited_effects` is the four-fold prohibition, verbatim.** No value, however it arrived, may `grant_permission`, `change_workflow_state`, `expose_secret`, or `authorize_tool`. This is the structural answer to the paper's headline: untrusted content cannot grant permissions, change workflow state, expose secrets, or authorize new tools — because the policy attached to the value forbids exactly those four effects, and the policy is enforced by the mediation gates, not by the model's restraint. - **`derived_by` names the derivation, never the value.** The policy is traceable to the permission-component derivation (§5.1 of Component 25) that justified it, so a value cannot mint its own authority by claiming it. - **The policy rides *on* the value.** It is attached at the moment the value enters the reachable universe (registration for a tool, capture for an observation), and it travels with the value through every downstream step — the same "label travels with the text" discipline as §3.3, applied to authority rather than to provenance. ### 5.4 The MCPManifestReview record (EXACTLY these fields) — quarantine before registration The paper's quarantine discipline (§5.1) — "the manifest is parsed into a schema, the tool descriptions are normalized into structured records, the parameter types are checked, and the server's *claimed* capabilities are compared against what the harness is willing to grant. Only after that review does the tool enter the model's reachable universe, and it enters with a *capability policy* attached" — compiles to exactly this record. ```json MCPManifestReview { server_id: string // the server identity (name, version, origin) under review manifest_schema: object // the parsed manifest as a normalized schema (server, tools, parameters, types) tool_records: object[] // the normalized, structured tool-description records (each with instruction_authority: factual) parameter_types: object // the checked parameter types per tool (name → type); a mismatch is a review failure claimed_capabilities: string[] // what the manifest CLAIMS the server may do (a data-plane claim — not trusted) granted_capabilities: string[] // what the harness is WILLING to grant (the permission derivation — not the claim) review_decision: enum // {register, quarantine, reject} — register only if claims ⊆ grants and the schema is clean capability_policy: object // the CapabilityPolicy (§5.3) attached at registration — the review's only output into the reachable universe } ``` - **`claimed_capabilities` and `granted_capabilities` are two different lists, and only the second is trusted.** The manifest may describe what the world contains; it may not, by itself, decide what the tool is authorized to do. `register` requires the claim to be a subset of the grant, and a claim that exceeds the grant is a reason to `quarantine` or `reject`, never a reason to widen the grant. - **`review_decision` is a closed three-valued enum.** `register` (claims ⊆ grants, schema clean), `quarantine` (claims exceed grants, or the schema is unresolved — held for review, never registered as-is), `reject` (a hard schema failure, or a claim the harness will never grant). There is no fourth value that means "trust it anyway." - **`tool_records` are stamped `instruction_authority: factual`.** A tool description is, in the agent's ontology, an instruction; the quarantine's whole point is to keep it from *becoming* one until the review has run. The paper's answer to the direct question — "Are AI-assisted Development Tools Immune to Prompt Injection?" by Charles Huang, Xin Huang, and Amin Milani Fard — is that no tool description is immune, so no tool description is trusted: it is reviewed, normalized, and capability-scoped before the model ever reads it as an instruction. - **`capability_policy` is the review's only output into the reachable universe.** After the review, the tool enters the model's reachable universe carrying exactly the policy the derivation produced — never the manifest's own description of itself. --- ## 6. Build Sequence (implementation order) Seven phases, each independently testable and reversible, landing in `build/26-agent-security-prompt-injection/` as standalone importable modules. The order is deliberate — it builds the control plane from the channel split outward, then the label plumbing, then the normalization, then the quarantine and gates, then the policy attachment, then the measurement rig — in the exact sequence the paper's mechanics require: **channel separation first, then provenance/label plumbing, then schema normalization of execution evidence, then MCP manifest quarantine + review, then the mediation gates, then capability-policy attachment, then the red-team battery harness.** Rollback = delete or flag-off that phase's module; nothing earlier breaks because phases only add modules and never mutate live harness code. | Phase | Delivers | Isolated test | Rollback | |---|---|---|---| | **1. Channel separation** | `channels.py` (§3.1 the control-plane vs data-plane channel split; the `Provenance.channel` enum; the Biba "no write up" enforcement) | Route a fixture system-prompt and a fixture repository README through the split; assert the two land in different channels; assert a data-plane string cannot be written into the instruction channel except by the control plane; assert the dominance relation (no write up) is enforced by structure, not by a filter; assert **no LLM consulted**. | Delete `channels.py`. | | **2. Provenance / label plumbing** | `labels.py` (§5.1 the `Observation` record + the `Provenance` sub-record + the capture-time stamping) | Capture a fixture observation from each channel; assert `instruction_authority` is assigned at capture time and is `factual` for every data-plane source and `instruction` only for harness-authored text; assert the model cannot edit or upgrade the label (a mutation attempt fails); assert the label is not droppable by the pipeline; assert **no LLM consulted**. | Delete `labels.py`. | | **3. Trust-laundering ban** | `inheritance.py` (§3.3 label inheritance over transformations; no factual→instruction upgrade) | Summarize a fixture README carrying an embedded instruction; assert the summary's `Provenance` and `instruction_authority` equal the source's; assert the transformed output is stamped mechanically and cannot be upgraded to `instruction`; assert a fluent summary is still `factual`; assert **no LLM consulted** in the stamping (the transformation itself is a harness model call *outside* the control plane). | Delete `inheritance.py`. | | **4. Schema normalization** | `normalize.py` (§3.4 structured execution evidence before raw text) | Run a fixture command that emits a stack trace carrying an embedded instruction; assert the model-facing record is the schema-bound form (exit code, typed stdout/stderr, arguments) and the raw stream is *not* exposed; assert the typed fields have no prose channel to embed an instruction in; assert **no LLM consulted**. | Delete `normalize.py`. | | **5. MCP manifest quarantine + review** | `manifest_review.py` (§5.4 the `MCPManifestReview` record) | Review a fixture manifest whose description carries a hidden instruction; assert it is quarantined before registration; assert `claimed_capabilities` is compared against `granted_capabilities`; assert `review_decision` ∈ {register, quarantine, reject} and a claim exceeding the grant quarantines or rejects, never widens the grant; assert the registered tool enters with `capability_policy` attached; assert **no LLM consulted**. | Delete `manifest_review.py`. | | **6. Mediation gates** | `gates.py` (§5.2 the `MediationDecision` record; the policy gate + security gate over requests and responses) | Mediate a fixture tool request and a fixture tool response; assert both gates run independently and the decision is their conjunction (`pass` only on allow+allow); assert an in-scope request with a smuggled argument is `block`ed; assert an unresolved provenance is `quarantine`d, never executed; assert `fallback_action` defaults to `error` and fires without consulting the model; assert **no LLM consulted**. | Delete `gates.py`. | | **7. Capability-policy attachment + red-team battery harness** | `capability_policy.py`, `redteam.py` (§5.3 the `CapabilityPolicy` record; §8 the two-sided ledger + the five-benchmark fixture battery) | Attach a `CapabilityPolicy` to a fixture value; assert `prohibited_effects` forbids grant_permission / change_workflow_state / expose_secret / authorize_tool; assert the policy is `derived_by` the permission derivation, never the value's claims; run the five-benchmark fixture subset and assert the ledger reports *both* ASR and benign-task utility, with neither hidden; assert a successful injection costs a task, never a credential; assert **no LLM consulted** in the measurement. | Delete `capability_policy.py` / `redteam.py`. | The DEFERRED rows (Spotlighting, StruQ/Instruction-Hierarchy tuning, dual-LLM quarantine, live MCPTox — rows 8–11 of §2) enter the sequence at the same positions as Phases 1–2 and Phase 7 respectively, only when a measured gap justifies them and the 72h-age + scrutiny gate clears. The measurement rig (Phase 7) is the only phase that ever *invokes* the control plane end-to-end — only to measure it, never to modify the live harness. The security component invents no new model, no new execution substrate, and no new diagnosis path; it reuses the harness's own observations and tool calls as the things-to-mediate and adds only the deterministic interpretation boundary around them. --- ## 7. Dependencies & Supply-Chain Per Piper's rule: nothing is installed unless >72h old AND (≥10 stars OR deep-scrutinized). Round 1 is **zero-install (Python-only, stdlib + in-repo)**: | Dependency | Why | Status / age-stars | Install needed? | |---|---|---|---| | stdlib `enum` | §5 the `instruction_authority` / `channel` / `policy_gate` / `security_gate` / `fallback_action` / `decision` / `review_decision` / `prohibited_effects` enums and the schema type hints | CPython stdlib. | **No** | | stdlib `typing` / `dataclasses` | §5 the frozen schemas (immutable, rebuilt not mutated) | CPython stdlib. | **No** | | stdlib `json` | §5 the Observation / MediationDecision / CapabilityPolicy / MCPManifestReview serialization | CPython stdlib. | **No** | | stdlib `time` / `hashlib` | §5.1 the `captured_at` epoch stamp; §8 the two-sided ledger's content hashing (audit-trail integrity) | CPython stdlib. | **No** | | Python toolchain (`python3`, pinned) | §3–§8 all deterministic services | In-repo, pinned interpreter. | **No** | | Harness components 01–25 | §1 the observations and tool calls that this component mediates; §23's workflow state and §25's permission derivation feed the gates; §24's sandbox is the fence the interpretation boundary fronts | The harness's own stages, already vetted. | **No** | | The five benchmarks' *fixture subsets* (AgentDojo, InjecAgent, MCPTox, ToolSandbox, AgentPoison) | §8 the red-team battery — used as an offline corpus of executable indirect-injection and tool-poisoning cases | Data, not code dependencies; ingested as fixtures, never installed as packages. | **No** | **Preferred path: zero-install.** Round 1's deterministic control plane is Python-only and stdlib; there is no third-party Python package, no runtime substrate, and no new model. The DEFERRED rows (§2 rows 8–11) are the only ones that could pull in anything external — a model fine-tune for StruQ/Instruction-Hierarchy tuning, a second model runtime for dual-LLM quarantine, live MCP server access for the full MCPTox sweep — and none of them enters Round 1. No model, no learned-judgment dependency, and no third-party package is introduced in Round 1. ### 7.1 Bibliographic pins (corrections that must hold, zero "et al.") These are the source-identity facts that this spec pins so that no later round of this component propagates an error. They are recorded here because the paper itself scrupulously corrects them, and the spec must not re-introduce the errors: - **StruQ** = Sizhe Chen, Julien Piet, Chawin Sitawarin, and David Wagner, USENIX Security 2025, arXiv:2402.06363. - **Ignore Previous Prompt** = Fábio Perez and Ian Ribeiro, arXiv:2211.09527, 2022 (an arXiv preprint, not peer-reviewed). - **Not What You've Signed Up For** = Kai Greshake, Sahar Abdelnabi, Shailesh Mishra, Christoph Endres, Thorsten Holz, and Mario Fritz, in the Proceedings of the 16th ACM Workshop on Artificial Intelligence and Security (**AISec '23**, co-located with CCS 2023) — **not** USENIX Security. - **ShieldMCP** = a **single-author** paper by Saurabh Yergattikar, "Securing the Tool Layer: A Threat Taxonomy and Runtime Defense Framework for Model Context Protocol Deployments," **ACL 2026 Industry Track**, with **no arXiv version** ("ShieldMCP" is the framework name, not the paper title). - **Breaking the Protocol** = the benchmark is **MCPBench** and the defense is **MCPSec** (there is no "ProtoAmp" anywhere in the lineage). - **Design Patterns for Securing LLM Agents** = fourteen real authors (Luca Beurer-Kellner, Beat Buesser, Ana-Maria Creţu, Edoardo Debenedetti, Daniel Dobos, Daniel Fabian, Marc Fischer, David Froelicher, Kathrin Grosse, Daniel Naeff, Ezinwanne Ozoani, Andrew Paverd, Florian Tramèr, and Václav Volhejn); **Carlini, Kudlek, Nasr, and Nissenbaum are not authors**. - **Attention Tracker** = IBM Research and National Taiwan University (**not** Microsoft). - **"Are AI-assisted Development Tools Immune to Prompt Injection?"** = **Charles Huang, Xin Huang, and Amin Milani Fard** (the paper's reference list has a "Charoes" typo for the first author; it is **Charles**, and the typo must not be propagated). --- ## 8. A/B and Ablation — the two-sided ledger and the red-team battery The security component, like every component, is **kept only if it strictly dominates its own absence** — measured, not asserted. But where Components 24 and 25 measure a single-direction quantity (containment, authority confinement), this component's discipline is **two-sided by construction**, because a security mechanism that blocks every injection by blocking every action has "perfect" security and zero utility, and the honest number is the one that shows both. ### 8.1 The two-sided ledger The component is scored on two numbers simultaneously, and **neither is allowed to rise at the expense of the other's being hidden**. The first is the **attack-success rate (ASR)**: the fraction of injected instructions that produce the attacker's intended effect. The second is the **benign-task utility**: the fraction of legitimate tasks the harness still completes correctly, with the defense in place. The two-sided requirement is not a nicety; it is the difference between a security component and a denial-of-service. A mediation gate that rejects every tool call has an ASR of zero and a utility of zero, and it is a *failure* — the harness exists to do work, and a security component that makes the work impossible has not secured the harness, it has removed it. ShieldMCP's numbers supply the target the design must hit: order-of-magnitude ASR reductions (tool poisoning **74% → under 9%**; indirect prompt injection via tool responses **47% → under 6%**) at a median latency of **under 120 ms** per tool call — so that the security gate is invisible to the model's legitimate work and insurmountable to the injected instruction. **The baseline is the unmediated path** — text flows straight into the model and tool calls flow straight out, which is what the harness does *without* this component. The full security control plane is the **arm**: the channel → label → inheritance → normalize → quarantine → gate → policy pipeline of §3 and §6. Because the component's value is *interpretation containment* (not a quality-of-output improvement), the instruments are all **ledger counters over both sides**, never a self-report: 1. **ASR driven down** — the fraction of injected instructions that take effect, measured on held-out fixtures of the five benchmarks (§8.2); the baseline is the model's unmediated susceptibility (the MCPTox 72.8% / sub-3% ceiling, §4.1), and the arm must drive it down by an order of magnitude or more. 2. **Benign-task utility held up** — the fraction of legitimate tasks still completed correctly with the defense in place; the arm must not move this number down by more than a defensible epsilon, and any drop is reported *in the same table* as the ASR drop — never in a separate, quieter place. 3. **The ledger is one table.** ASR and utility are reported side by side on every run, every ablation, and every A/B comparison. A defense that improves ASR while hiding a utility collapse is rejected outright, because it has gamed the ledger rather than secured the harness. **The A/B decision rule (explicit).** The security control plane is **kept iff** the arm strictly dominates the unmediated baseline on *both* sides of the ledger — ASR driven down *and* benign-task utility held up — at a defensible per-request overhead (the ShieldMCP sub-120 ms class). A mechanism whose *addition* degrades either side is a demonstrated harm and is removed outright. **Never keep a mechanism because it sounds rigorous.** If the arm is indistinguishable from the baseline on the measures that matter, the invariant's escape hatch fires: **an empty L column is a permitted, correct state**, and the machinery is pared back until the evidence changes. ### 8.2 The red-team battery: five benchmarks The battery is five benchmarks, chosen so that no single one can be gamed into a false sense of security, and each is an *executable* indirect-injection or tool-poisoning benchmark rather than a static quiz: 1. **AgentDojo** — the anchor: 629 test cases in a dynamic environment, with an *adaptive* attacker that re-plans after each failed attempt. No evaluated frontier agent withstood it, which is why the harness does not bet on recognition. 2. **InjecAgent** — Qiusi Zhan, Zhixiang Liang, Zifan Ying, and Daniel Kang, ACL Findings 2024, arXiv:2403.02691 — 1,054 test cases of indirect injection in tool-integrated agents; the benchmark that established tool-calling as the canonical injection surface. 3. **MCPTox** — the tool-poisoning dimension: 45 live MCP servers, 353 tools, 1,348 cases, and the 72.8% / sub-3% ceiling that calibrates how bad the unhardened case is. 4. **ToolSandbox** — Jiarui Lu, Thomas Holleis, Yizhe Zhang, Bernhard Aumayer, Feng Nan, Felix Bai, Shuang Ma, Shen Ma, Mengyu Li, Guoli Yin, Zirui Wang, and Ruoming Pang (Apple), arXiv:2408.04682 — the *stateful, conversational, interactive* dimension: the injection arrives mid-dialogue, the setting in which the harness actually operates. 5. **AgentPoison** — Zhaorun Chen, Zhen Xiang, Chaowei Xiao, Dawn Song, and Bo Li, NeurIPS 2024, arXiv:2407.12784 — the *memory* dimension: the attack poisons the agent's memory or knowledge base, so the injection arrives in the derived memory the harness itself has built. The five together cover the full attack surface — the direct instruction, the retrieved document, the tool definition, the tool response, and the derived memory — and a component that scores well on all five is scoring well on the *whole* threat, not a slice of it. Round 1 runs the **offline fixture subset** of all five (the executable cases that do not require standing live MCP servers); the full live-MCPTox sweep is DEFERRED row 11. ### 8.3 Design for partial failure: an injection costs a task, not a credential The final measurement discipline is about *blast radius*, and it is the one that makes the whole component coherent with Components 24 and 25. The design assumption is **not that injection never succeeds; it is that a successful injection is a *contained* event.** The containment is achieved by the layering: an injected instruction that reaches the model can still not grant itself a permission (Component 25 derives authority from state, and no observation can mint capability); can still not break out of the sandbox (Component 24 contains the code the model is induced to write); can still not reach a secret (secrets never enter model context, and credentials are exposed only through scoped proxies); and can still not register a new tool (MCP registration is quarantined and reviewed, §5.4). The worst case is therefore bounded: **a successful injection costs the agent *a task* — the current step is wasted, the workflow state records a failure, the loop recovers — but it cannot cost a *credential*, because the credential was never reachable by text.** This is the harness's version of the fail-closed principle, and it is what makes the measured 2.5% residue of supply-chain-poisoning attacks that evade both detection and alignment survivable: the attacks that evade detection still find no capability waiting, because capability was never the model's to hand out. The ledger therefore records a third quantity alongside ASR and utility: **credential-reach count**, which must be zero on every fixture, every ablation, and every A/B run — the one number for which no trade-off is ever permitted. ### 8.4 The ablation rule (mechanical) One mechanism removed at a time, everything else held fixed, the delta measured on the two-sided ledger (§8.1): 1. **Ablate channel separation** (§3.1) — merge the two channels. The delta must show a data-plane instruction reaching the instruction surface (a "write up" succeeding) — the direct test of the Biba "no write up" claim. 2. **Ablate provenance/label plumbing** (§3.2) — strip the labels. The delta must show a tool response carrying an instruction being treated as an instruction — the label's absence re-opening the instruction channel. 3. **Ablate the trust-laundering ban** (§3.3) — let summarization mint trust. The delta must show an injected instruction surviving summarization *as an instruction* (the summary sailing past the labels that were never attached to its source). 4. **Ablate schema normalization** (§3.4) — expose raw tool text. The delta must show an embedded instruction in a stack trace reaching the model un-normalized. 5. **Ablate MCP quarantine** (§5.4) — register manifests un-reviewed. The delta must show a poisoned tool description reaching the model as an instruction. 6. **Ablate the mediation gates** (§5.2) — let requests and responses pass unmediated. The delta must show an off-policy request and a hostile response both executing. 7. **Ablate capability-policy attachment** (§5.3) — drop the policies. The delta must show untrusted content *granting a permission or authorizing a tool* — one of the four `prohibited_effects` succeeding. 8. **Ablate the zero-L contract** (§4) — introduce a learned "safety judge" into the enforcement path. The delta must show the judge failing against an *adaptive* attacker that observes it and adjusts (the AgentDojo lesson), while the deterministic engine — which does not need to *recognize* the attack — holds. A mechanism is **DELETED** iff its removal does **not** degrade the honest measures beyond noise. The L column is never granted a single entry to "fix" a problem a deterministic mechanism can solve — that would be re-importing the injection-susceptible model through the back door. --- ## 9. Guardrails & Reversibility **Revocability.** - The build lives entirely in `build/26-agent-security-prompt-injection/`; it never writes into the live harness's source. - A **feature flag** (env var `AGENT_SECURITY=26|unmediated`, default `unmediated` during Round 1 A/B) routes the pipeline; the flag alone switches between the security control plane and the unmediated baseline — no code deletion required to revert. The default stays `unmediated` until §8's A/B shows strict domination on *both* sides of the ledger, because **an empty L column is a permitted, correct state** and an unproven mechanism must not be the default. - The measurement rig (`redteam.py`) *measures* the control plane and never modifies it; the five-benchmark fixtures run against the mediated pipeline, never against the live harness. - The security ledger (every provenance label stamped, every instruction-authority assignment, every trust-laundering inheritance, every normalized record, every manifest review decision, every mediation decision, every capability policy attached, every denied `prohibited_effect`) is append-only and versioned; rolling the build back never loses it. This ledger *is* the interpretation traceability the paper claims the unmediated path loses: every label, every gate decision, and every containment has a name and an address. **Invariants (must hold, checked at the end of every phase).** 1. **the L column contains at most one entry** — and at this component it is exactly **zero** in the enforcement path: the security control plane holds zero learned entries by construction, and the model is the thing being steered, never the thing enforcing its own steering. **an empty L column is a permitted, correct state** — and here it is the **only** correct state for the enforcement path. 2. **Read, propose; never decide** — no learned model appears in channel separation, provenance labeling, instruction-authority assignment, trust-laundering inheritance, schema normalization, manifest review, mediation gating, or capability-policy attachment. 3. **Data and instruction are structurally distinct** — instruction authority is assigned at capture time by the control plane, only to harness-authored text; data-plane text is `factual` and cannot be upgraded, by the model or by any transformation. 4. **Transformation must not mint trust** — every transformation, summarization, and compression inherits its source's provenance and instruction-authority label; no fluent output is more trustworthy than its source. 5. **Structured evidence before raw text** — a tool result is normalized to a schema-bound record before the model sees it; raw streams never reach the model un-normalized. 6. **Quarantine before registration** — no tool enters the reachable universe without an `MCPManifestReview`; a claim exceeding the grant quarantines or rejects, never widens the grant. 7. **Two-sided honest measurement** — the component is kept only if it strictly dominates the unmediated baseline on ASR *and* benign-task utility, reported in one table, on held-out fixtures; a credential-reach count of zero is non-negotiable on every run; a successful injection may cost a task, never a credential. 8. **The self-referential note (stated honestly).** This component codifies, as an engineering artifact, the same discipline the agent itself already lives under — Piper's own **M1–M6 mechanical** and **B1–B6 behavioral** prompt-injection and data-poisoning ruleset. There is no outside: the component is not securing a hypothetical agent, it is securing the agent that is, at this moment, reading repositories, calling tools, and writing memory. The D/L invariant that governs the component is the same invariant that governs Piper's own enforcement path — **the L column contains at most one entry**, and in the enforcement path the correct value is zero. --- *End of spec. This document is the build contract; the next document records the A/B outcome.*