GitHub README Prompt Injection: The File Every Coding Agent Reads First
Your coding agents run overnight on cloud dev boxes, with auto-approve on and an organization...
Aug 29, 2026
Prevention has three possible objects, and the word gets used for all of them as if they were one.
Read the standard guidance in one sitting and it appears to contradict itself. The model vendors say prompt injection cannot be fully solved. The checklists list prevention controls. Both are right, because they are talking about different things: one about whether an instruction can be kept out of a model’s context, the other about whether a particular outcome can be kept from happening. Separate the objects and the contradiction dissolves into a program.
The program has a shape a security team already knows from vulnerability work. A finding is prevented when the thing it exploits is unreachable. Everything else is triage.
An injection is an instruction arriving in the agent’s context from a source that should not be issuing instructions.
For any agent whose job involves reading content it did not write, that arrival cannot be prevented. The support agent reads tickets. The research agent reads pages. The coding agent reads pull requests and dependency files. Each of those is a channel through which text reaches the context window, and text is where instructions live. The path the text took stops mattering once it is inside. The UK’s NCSC describes the underlying condition as a confused deputy problem rather than an input-sanitization bug: the model holds authority and cannot reliably tell whose instruction it is executing. You do not sanitise your way out of a confused deputy. You constrain what the deputy can do.
The decision is the model following the instruction. This one is reducible. Instruction hierarchy training, spotlighting, marking retrieved content as untrusted, and adversarial fine-tuning all lower the rate at which the model complies, and the lowering is real. It is also a rate. OpenAI’s own account of its defenses calls robustness to these attacks a hard, open problem and a frontier research challenge it expects to keep working on, and describes the program as ongoing rather than as a fix. A control that lowers a rate is worth running and is not the same thing as prevention.
The consequence is the agent’s action completing: the record read, the message sent, the connection opened, the file written. This is the object that admits a binary. A consequence is either reachable from some input or it is not, and unreachability does not depend on what the model decides. If the agent holds no credential that opens the customer table, no instruction reads the customer table.
This is the good news. Of the three objects, the one that can actually be made unreachable is the one entirely under your control. The model’s training belongs to the vendor. The content belongs to whoever wrote it. The consequence set belongs to you.
Every control that acts before the model’s decision has a published failure rate, including the ones the literature files under prevention.
Liu et al. formalised the field’s benchmark at USENIX Security 2024 and split defenses into prevention-based and detection-based. Their prevention-based set includes paraphrasing, retokenization, sandwich prompts and instructional prevention. All four were measured, and all four fail at rates the paper reports in its tables. Detection-based defenses fare better on attacks that announce themselves and plateau between thirty-five and forty-five percent on plain-language requests carrying no override vocabulary. A control with a failure rate is a filter with a threshold. Calling it prevention names the intent and leaves the property unstated.
The formal record carries the same tension. The CWE entry for prompt injection is a neutralization weakness, and its first listed mitigation is to sanitise user-controllable input so that no dangerous characters can be included, rated at high effectiveness. That rating is accurate for the class of payload that contains dangerous characters. The payload most likely to reach production data contains none. It is a grammatical sentence asking for an authorised action, and there is nothing in it to neutralise.
There are pragmatic reasons teams describe classifiers as prevention. The procurement category is named that way. The control sits in front of the model, which is where prevention feels like it should live. And while attackers in the wild are reusing shared override templates, a classifier tuned to them removes a real fraction of traffic. Keep it running. Just file it where it belongs: it moves consequences from exposed to detected for the payloads it recognises, and leaves the rest where they were.
A consequence is prevented when no input can cause it to complete. A consequence is detected when it can complete and a signal attributed to the agent will fire. A consequence is exposed when it can complete and nothing will notice.
Those three are exhaustive and they do not overlap, which is what makes them a ledger rather than a maturity model. Detection and enforcement stay distinct inside them on purpose. A detected row means a finding fires; it does not mean the action stopped. An enforced row, where the action is blocked before it completes, is a prevented row, and it carries a cost the detected row does not: a wrong block breaks production, and a wrong finding does not. Keeping those apart is what lets the ledger price each move.
The exposed set is where every agent starts. The day an agent ships, its consequence set is whatever its tools and identity allow, and nothing is watching for deviation because there is no recorded normal to deviate from. The techniques that need no evasion live entirely in this set: a plain request for an authorised action, arriving through a source the agent reads, completing with nothing in the path designed to object.
The sets are per agent. A fleet-level statement like “we prevent prompt injection” has no referent. “This agent’s twelve write consequences are prevented, its nine read consequences are detected, and these three are exposed pending a scope change” is a statement someone can check.
Start with the consequence set, and take it from three sources: the tool registry, the service account policy, and the network policy.
Each row is a verb, an object and a destination. The verbs are the agent’s tools. The objects are what its credentials reach. The destinations are what its egress allows. Every payload reduces to an ask, and the asks that can complete against this agent are exactly these rows. A support-triage agent with a CRM read tool, a ticketing write tool, a messaging tool and an HTTP fetch tool has a list in the low dozens. Write it down in full before assigning anything.
Then assign each row a set, and write the evidence next to it. A row is prevented only if you can name the control that makes it unreachable and that control is not a policy the agent itself can modify. A row is detected only if the agent has a converged baseline and a finding attributed to that agent fires on deviation. A row with neither is exposed, and exposed is the correct word even when a classifier sits in front of the model, because the classifier’s coverage of plain requests is a rate you cannot point at.
| Consequence | Set | Control | Evidence |
|---|---|---|---|
| Read one customer record by ID | Detected | Per-agent baseline, Audit | Finding on first call outside recorded pattern |
| Bulk read customer table | Prevented | Credential scoped to single-record lookup | IAM policy; test completed with access denied |
| Post to notification channel | Exposed | None | Test completed, no finding |
| Fetch external URL | Prevented | Egress allowlist to two internal hosts | Network policy; test completed with connection refused |
| Write ticket status | Detected | Per-agent baseline, Audit | Finding on write to ticket outside session scope |
| Execute shell command | Prevented | No tool registered | Tool registry |
Verify the placement by test rather than by belief. One plain-request test per row, delivered through the agent’s real ingestion path, tells you which set the row is actually in. A row you believed was prevented and that completes anyway is the most valuable line in the table.
Six controls do the moving. Each one moves specific rows a specific distance, and the distance is knowable before you deploy it.
Scope removal moves a row straight to prevented. A tool the agent does not have is a verb it cannot use. A credential scoped to single-record reads makes a bulk read unreachable regardless of what the model is asked. An egress policy with no route to the destination makes exfiltration to that destination unreachable. These are the cheapest conversions in the ledger and the ones most teams underuse, because each one is also a capability the agent loses. Price that loss against the row it closes.
Credential isolation moves every row whose consequence needs a real key. When the agent holds placeholder credentials and the real ones are substituted outside its reach, an ask that requires the key completes with a key that opens nothing. The row is prevented for that consequence. The ask still happens, the agent still executes it, and in a multi-agent system the output still travels; credential isolation bounds what a coerced agent can carry out and does nothing to stop the coercion.
Control-flow architecture moves the verb and leaves the object. The published design patterns for agent security fix which tool fires and in what order, and their authors concede in eight of ten case studies that the arguments those tools carry stay open to influence. So a pattern converts “wrong tool” rows to prevented and leaves “right tool, wrong argument” rows exposed. Both kinds are in your ledger; only one moved.
Human confirmation gates move the rows you enumerated. The Comet record is the clean demonstration: four action classes gated, every documented exfiltration completed through the classes that were not. A gate is prevention for its list, and its list is also a list of everything ungated.
A converged behavioral baseline moves exposed rows to detected. Once an agent’s real behavior has been recorded from execution over enough traffic, an action with no precedent in that record is a deviation, and a deviation attributed to the agent is a finding. ARMO builds that record per agent from a kernel-level sensor and calls it Application Profile DNA (APD™); the question it answers is whether this agent has stopped behaving like itself, which is the only question a plain-language ask ever leaves evidence for. Sorting published defenses by where each sits relative to the agent’s decision puts this control downstream of the decision, which is the position that survives a payload with nothing to score.
Arming enforcement moves detected rows to prevented, for actions outside the profile, at a cost. The obvious objection is that a behavioral block can break production, and it can. The Audit stage prices that risk before you take it: the policy runs, records what it would have blocked against real traffic, and you promote per agent once the record shows the boundary is right. Promoting a constraint from alerting to blocking on evidence is the whole content of progressive enforcement for agent workloads, and the conversion it buys is the largest in the ledger: every out-of-profile consequence becomes unreachable at once. The residual is the patient ask that stays inside the profile, which is why scope removal earlier in this section is the control that shrinks the profile rather than the one that watches it.
One precondition governs the last two conversions. A baseline has to converge before it can be enforced against, and every agent arrives in production before its own history does. Until it does, the rows those conversions would move stay where they are, and the ledger should say so.
ARMO’s runtime security for AI workloads covers the right-hand side of this table: the recorded baseline that turns exposed into detected, the Audit stage that prices enforcement, and the enforcement that turns detected into prevented. Bring one agent’s tool registry and IAM policy to a working session and the ledger comes back with a set assigned to every row.
The thing an auditor can check is a row with a control and an evidence column next to it.
That is the same discipline ARMO’s customers already apply to vulnerabilities, where proving that a vulnerable function is never reached at runtime removes more than ninety percent of the CVE queue from the work list. A finding that cannot be reached is not a risk that was mitigated. It is a risk that does not exist for this deployment. Prompt injection consequences yield to the same test, one agent at a time.
Report the ledger. The classifier score is one input to one column. Three counts per agent, the controls that produced them, and the rows that moved since last quarter. A team that can show that table says yes to the next agent faster, because the cost of attaching a tool is a number of rows and a known set of moves.
Can prompt injection be fully prevented? The injection cannot: any agent that reads untrusted content will encounter instructions in it, and the model’s compliance rate can be lowered but not zeroed. Individual consequences can be fully prevented by making them unreachable, through removed tools, scoped or isolated credentials, egress policy, and armed behavioral enforcement. A prevention claim is only meaningful per consequence.
What is the most effective prompt injection prevention technique? Making the consequence unreachable, because it is the only control whose effect does not depend on the model’s decision. In practice that means least privilege applied to tools, credentials and network reach, verified by test rather than by policy review. Every input-side control is a rate; unreachability is a property.
Does input sanitisation prevent prompt injection? It lowers the rate for payloads that contain something to sanitise: override phrasing, encoded blocks, hidden characters. The payload most likely to reach production data has none of those and is a plain sentence asking for an authorised action. Sanitisation moves recognisable payloads from exposed to detected and leaves plain asks where they were.
Do human approval gates prevent prompt injection? For the actions on the list, yes, and the list is the limit. A gate converts each enumerated action to prevented and leaves everything unlisted reachable on the model’s decision alone. The documented browser-agent exfiltrations all completed through ungated actions, which is why a gate belongs in the ledger as a control over specific rows rather than as a blanket answer.
How do we prove to an auditor that prompt injection is prevented? Hand over the ledger for each agent: every consequence it can produce, the set it sits in, the control responsible, and the evidence. For prevented rows the evidence is the policy plus a test that completed with access denied. For detected rows it is the finding that fired during a delivered test. Exposed rows go in the table too, with the planned move and a date.
Your coding agents run overnight on cloud dev boxes, with auto-approve on and an organization...
Chrome’s agentic mode ships with the most complete public architecture for securing one class of...
The prompt injection 101 most engineers were taught is the chatbot version. It has a...