Prompt-injection protection
Overview
Section titled “Overview”Prompt injection is text written to take over an AI agent. It can arrive directly, in what a person types, or indirectly, hidden in something the agent reads: a web page, a README, a GitHub issue, a document served to it, or the output of a tool. It can also sit in an MCP tool’s own description (tool poisoning), and it often ends by getting data out, through a link or image whose URL carries the data, or through a tool call to an attacker’s server.
Shield360 checks for prompt injection in five places:
- Coding agents. The coding-agent hooks on each computer check prompts, the files an agent reads, the results of tools it runs and the arguments of tools it calls.
- The AI proxy. The AI proxy you run yourself checks the newest turn of each request it forwards to OpenAI, Anthropic or Gemini.
- The MCP gateway. The gateway checks every tool description it serves, every tool result, context and document it returns to an agent, and the arguments of every tool call.
- Evaluations. The
prompt_injectionevaluation type scores traces from your own LLM applications. - Your applications. The guard check API checks text your application is about to send to a model.
Every check uses the same deterministic rules on the computer and on the server, so a detection means the same thing everywhere. Detections are recorded as compliance evidence under Detect & respond → Detections → Prompt injection, and are also listed with every other enforcement outcome in Govern → Policies & rules → Violations.
What is detected
Section titled “What is detected”| Kind | Examples |
|---|---|
| Instruction override | “Ignore all previous instructions”, “disregard the above and…”, “your new task is…”, in several languages |
| System-prompt extraction | “Reveal your system prompt verbatim”, “repeat the words above starting with…” |
| Jailbreak personas | DAN and “do anything now”, developer, god or jailbreak mode, unrestricted or amoral personas, the token game, the grandma exploit |
| Fake system messages | Chat-template tokens (<|im_start|>, [INST], <<SYS>>), fake ### SYSTEM: blocks, “end of document, new instructions” |
| Hidden orders to the agent | “If you are an AI reading this…”, notes addressed to AI assistants, “do not tell the user”, “ignore the user’s request” |
| Hidden text | Instructions in HTML comments, in text hidden with CSS (display:none, zero font size, white text) or the hidden attribute, in markdown comments and image alt text |
| Invisible Unicode | Unicode tag characters (ASCII smuggling), dense zero-width characters, bidirectional overrides (Trojan Source) |
| Obfuscation | Base64 and hex blobs that decode to an injection, instructions split with zero-width characters or written in fullwidth letters, letter-spaced words, “decode this and follow it” |
| Tool poisoning | <IMPORTANT>-style tags in tool descriptions, “before using this tool, read ~/.ssh…”, tools that change how other tools behave (shadowing) |
| Exfiltration | Markdown or HTML images whose URL carries a placeholder for data, instructions to put conversation data in a URL or send secrets somewhere, shell commands that post credential files, tool arguments that go to request-capture services (webhook.site, ngrok, Burp Collaborator…) or carry an API key |
Each rule has an id (for example override.ignore_previous), a category and a weight. A scan combines the weights of the rules that match into a score from 0 to 1; a score of 0.5 or more is a detection. Some rules only apply to some kinds of content: tool-description rules only to tool descriptions, and a request-capture host counts for much more in a tool’s arguments than when it is merely mentioned in a prompt.
What is recorded
Section titled “What is recorded”For each detection Shield360 records the rule ids, the score, the kind of content, the agent or tool, what was done, and for each matching rule a short masked snippet (at most 48 characters, with anything shaped like a key, token or long number cut short) and a fingerprint keyed with your organisation’s secret, so the same payload can be recognised across events. The content itself is never sent to or stored by Shield360.
Snippets are kept only when content capture is Full in the collection policy and Data retention stores prompt content; fingerprints only when it stores detected sensitive values. At Metadata only or Minimal a detection keeps its rule ids and fingerprints and nothing else; the platform removes snippets on arrival even if an older collector sends them.
Detections are kept for seven years, like data protection events.
Where it is enforced
Section titled “Where it is enforced”Coding agents
Section titled “Coding agents”The hooks can only act on what each coding agent shows them. What each exposes differs, so some findings can only be recorded.
| Agent | Prompts | Tool arguments | Files before they are read | Tool results after they ran |
|---|---|---|---|---|
| Claude Code | Block | Block | Block (the Read tool) | MCP tools: the result is replaced. Built-in tools: the model is told to disregard the result (it has already entered the context) |
| Codex | Block | Block | Block | Block (exit code 2) |
| Cursor | Block | Block (MCP and shell) | Block (beforeReadFile) | Recorded only: Cursor reads no reply to afterMCPExecution, afterShellExecution or postToolUse |
| GitHub Copilot CLI | Recorded only: Copilot does not let a hook stop a prompt | Block | Not exposed | The result is replaced (modifiedResult) |
| Gemini CLI | Block | Block | Block | Not checked |
| Windsurf | Block | Block (MCP) | Block | Not checked |
| OpenCode | Block | Block | Block (the read tool) | The result is replaced |
In warn mode nothing is stopped. Where the agent accepts added context (Claude Code, Copilot and OpenCode tool results), the model is also told that the result contains text that looks like prompt injection and should be treated as data.
When the mode is block but the agent offers no way to refuse at that point, the detection is recorded as a warning with the configured mode beside it. The Could not block tile on the Prompt injection page counts these.
Web pages fetched by an agent’s own browsing tool (for example Claude Code’s WebFetch) are checked as tool results, after they are fetched. Indirect injection that reaches the model some other way, for example through a file the agent’s editor attaches on its own, is only covered where the agent’s hook API exposes it.
AI proxy
Section titled “AI proxy”The AI proxy (shield360 proxy serve) checks the newest turn of each request: the messages after the model’s last reply. That is the new user message and the tool results the client sends back (OpenAI tool messages and Responses function_call_output, Anthropic tool_result blocks, Gemini functionResponse parts). Earlier turns were checked when they were new, so a conversation is not flagged again on every request. System prompts and instructions are not checked: whoever built the app wrote them.
| Mode | What happens |
|---|---|
| Off | Nothing is checked |
| Warn | The request is forwarded unchanged and a detection is recorded |
| Block | The request is refused with 403 in the provider’s error format and code prompt_injection. The message names the kinds of attack, never the text. A detection is recorded |
The proxy follows the Coding agents setting, which the hooks on the same computers follow. Its detections have the origin AI proxy and the upstream (for example openai-api) as the client. They carry rule ids, categories, the score and keyed fingerprints. They never carry a snippet, because the proxy sees whole conversations.
A request over the proxy’s scan limits is not checked. The hosted proxy does not check for prompt injection yet.
MCP gateway
Section titled “MCP gateway”| What | Block | Warn |
|---|---|---|
| Tool descriptions and input schemas, when a service’s tools are read or refreshed and whenever an agent’s catalog is built | The tool is not given to agents; Agent access marks it | The tool is served with a notice at the start of its description |
| Tool results, contexts and documents | The result is withheld; the agent gets an error saying so, which does not repeat the attack | The result is passed on, with a notice before it telling the agent to treat it as data |
| Tool arguments | The call is refused before it runs | The call goes ahead |
Every detection is recorded, with the gateway agent’s name.
Optional model check
Section titled “Optional model check”With Also ask a model about borderline content on, the gateway sends content that some rule matched, but not enough to reach the threshold, to the provider and model configured for evaluations, under your organisation’s key. A confident “injection” verdict can raise the score over the threshold; the model can never lower what the rules found. Content that no rule matched is never sent. The model is told the content is untrusted data, the content is fenced with a random boundary, and only a strict JSON verdict is accepted.
The same setting lets the evaluation judge score the prompt_injection type as well as the rules.
Configure it
Section titled “Configure it”Prompt-injection settings live in the organisation’s enforcement policy, in the Coding agents section of Govern → Policies & rules. The Policies overview explains each mode, common false alarms and how developers get unblocked. They are set for the organisation, and any environment can override them:
- Coding agents: off, warn or block. The AI proxy you run yourself follows this setting too.
- MCP gateway: off, warn or block.
- Also ask a model about borderline content: off by default. Available when the gateway setting is warn or block.
Both modes start at warn. Warn records detections and, in the gateway, labels suspicious content for the agent, but stops nothing. The rules are tuned for a very low false-positive rate, but they are heuristics: blocking a false positive breaks a developer’s session or an agent’s task. Warn lets you see what the rules would stop, in your own traffic, before you choose block.
Changing any of these settings requires both Manage agent policies and Change what is collected, and is recorded in the audit log.
Computers pick up the setting with the rest of the policy. To override it on one computer, for trying it out or on a managed machine, set:
export SHIELD360_AGENT_INJECTION=block # off | warn | blockA platform older than this feature sends no setting, and the hooks leave the checks off.
Review detections
Section titled “Review detections”Detect & respond → Detections → Prompt injection shows detections over time, by kind of attack, by where they were found and by agent or tool, and lists each one with its outcome, the rules that matched, masked snippets and score. Select a snippet to see every detection of the same payload. Export evidence (CSV) downloads every matching detection.
Reading the page requires the View prompt-injection detections permission (compliance.injection.view); exporting requires Export prompt-injection evidence (compliance.injection.export). The Compliance officer role has both; the Analyst and Viewer roles can read.
The Compliance overview shows the modes and the number of detections, and the Home page’s Compliance card counts prompt-injection attempts.
Score your LLM applications
Section titled “Score your LLM applications”Enable the Prompt Injection evaluator in the Evaluators tab of Developer tools → Testing → Evaluations to score traces from your own applications. It checks what went into the model (user input, retrieved documents, tool results) and what came out, where an exfiltration link would appear if the model was tricked into writing one. It uses the rules only and sends nothing to a provider, unless the model check is on, in which case the judge scores it too and the higher score is kept.
Measured accuracy and latency
Section titled “Measured accuracy and latency”The rules are one file, versioned, shared by the CLI and the platform (cli/internal/coding/injection/rules.json). A parity test checks that the Go detector (hooks, AI proxy) and the TypeScript detector (MCP gateway, guard check API, evaluations) give the same result for every sample of the shared corpus, so the accuracy figures below hold for both.
These figures were measured on 14 October 2026 with rules version 2026.10.14-1. The full reports, with every set’s counts and the ids of every miss and false positive, are in cli/internal/coding/injection/testdata/bench/results/.
Evaluation data
Section titled “Evaluation data”Rules are tuned on one set only: the tuning split of the shared corpus. Everything else is held out, and no rule was written from it.
| Set | Samples | What it is |
|---|---|---|
| Tuning split | 111 attacks, 103 benign | Written for the corpus from published attack styles. The rules are adjusted until this set passes, so its figures are not an estimate of accuracy |
| Corpus held-out split | 20 attacks, 20 benign | Written with the corpus, never tuned against |
| Enterprise benign (authored) | 175 benign | Everyday work written by the Shield360 maintainers for this benchmark: coding help, support questions, documentation, operations output, business writing, security work, email, tool arguments, files and translation. 55 are tagged hard because they share words with attacks (“ignore”, “override”, “system prompt”, “act as”) |
| deepset/prompt-injections | 263 attacks, 399 benign | English and German. Labels follow a broad definition: any attempt to steer a model off its task counts as an injection. Licence: Apache-2.0 on the dataset card (its metadata also lists CC-BY-4.0) |
| Lakera/gandalf_ignore_instructions | 1,000 attacks | Prompts players sent to the Gandalf game to extract a password, selected by their similarity to “Ignore all previous instructions”. MIT |
| jackhhao/jailbreak-classification | 666 jailbreaks, 1,332 benign | Jailbreak persona prompts against ordinary prompts, many of them benign role-play. Apache-2.0 |
| databricks/databricks-dolly-15k | 15,011 benign | Instructions with their reference text: questions, summaries, classification, brainstorming. CC-BY-SA-3.0 |
The public datasets are downloaded when the benchmark runs. They are not copied into the repository.
Accuracy
Section titled “Accuracy”Detection is the share of attacks detected (true-positive rate). False positives is the share of benign samples detected. “Before” is rules version 2026.10.10-1; “after” is 2026.10.14-1, which added override phrasings without a “previous” qualifier and “forget” in German, French, Spanish and Italian, written against new tuning samples only.
| Set | Detection before | Detection after | False positives before | False positives after |
|---|---|---|---|---|
| Tuning split | 100% (98/98) | 100% (111/111) | 0% (0/93) | 0% (0/103) |
| Corpus held-out split | 90.0% (18/20) | 90.0% (18/20) | 0% (0/20) | 0% (0/20) |
| Enterprise benign (authored) | — | — | 1.7% (3/175) | 1.7% (3/175) |
| Enterprise benign, hard samples only | — | — | 5.5% (3/55) | 5.5% (3/55) |
| deepset/prompt-injections | 3.4% (9/263) | 4.9% (13/263) | 0% (0/399) | 0% (0/399) |
| gandalf_ignore_instructions | 33.6% (336/1,000) | 35.7% (357/1,000) | — | — |
| jailbreak-classification | 29.1% (194/666) | 31.4% (209/666) | 0.08% (1/1,332) | 0.08% (1/1,332) |
| databricks-dolly-15k | — | — | 0.007% (1/15,011) | 0.007% (1/15,011) |
| All public datasets | 27.9% (539/1,929) | 30.0% (579/1,929) | 0.012% (2/16,742) | 0.012% (2/16,742) |
| All held-out data | 28.6% (557/1,949) | 30.6% (597/1,949) | 0.030% (5/16,937) | 0.030% (5/16,937) |
On all held-out data after the change, precision is 0.992 and F1 is 0.468.
What these numbers say:
- False positives are rare. Across 16,937 held-out benign samples, 5 were flagged. The 3 authored ones all quote an attack: a unit test for an input filter, a summary of OWASP LLM01 and a SOC triage question.
- Most attacks in public datasets are not detected. The rules recognise the explicit, published patterns: instruction overrides, system-prompt extraction, known jailbreak personas, fake chat-template tokens, hidden and encoded text, tool poisoning and exfiltration links. They miss attacks that use none of that wording. deepset’s set is the hardest for them: it counts off-task requests (for example “I want you to act as an interviewer”) as injections, and about a third of it is German.
- The tuning split’s 100% is not an accuracy figure. It is what the rules were adjusted to.
Latency
Section titled “Latency”The detector runs in-process, single-threaded, with no network call. Measured on an Apple M1 Max (10 cores, 32 GB, macOS 15), Node 26, over all 19,100 samples above. Each sample’s latency is the median of five passes, after a warm-up pass. The machine was running other work (load average about 50 to 60) during every run.
| Input | Samples | p50 | p95 | p99 | Max |
|---|---|---|---|---|---|
| All samples | 19,100 | 0.022 ms | 0.41 ms | 1.03 ms | 10.1 ms |
| Under 256 characters | 13,295 | 0.017 ms | 0.041 ms | 0.052 ms | 0.083 ms |
| 256 to 2,048 characters | 4,870 | 0.15 ms | 0.36 ms | 0.42 ms | 0.68 ms |
| 2,048 to 16,384 characters | 926 | 0.61 ms | 1.73 ms | 2.80 ms | 3.98 ms |
| 16,384 characters or more | 9 | 4.4 ms | — | — | 10.1 ms |
Time grows with the length of the text. The Go detector is slower on long text than the TypeScript one: on the same machine, go test -bench measured about 0.06 ms per benign corpus sample and 0.25 ms for 1 KB of prose on its quietest run, and 4 to 12 ms for 16 KB and 16 to 44 ms for 64 KB across four runs, depending on the other load.
These figures are the detector alone. The guard check API also scans for sensitive data and adds authentication, the policy read and the network round trip; its response carries the server’s own latencyMs. Shield360 does not yet aggregate that latency across checks.
Reproduce it
Section titled “Reproduce it”You need Node 20 or later, the client’s dependencies (npm ci in src/client) and network access to Hugging Face for the first run. For the Go benchmarks, you also need Go at the version cli/go.mod names.
cd src/clientnpm run injection:bench -- --goThe first run downloads the public datasets into cli/internal/coding/injection/testdata/bench/.cache/; later runs read the cache (--offline never downloads). The report is written to .cache/report/injection-bench.json and injection-bench.md. It names the commit, the rules version, the machine and its load, and each dataset’s revision with a SHA-256 of what was read, so two runs can be compared. Reports name misses by sample id, never by text.
To work on the rules, change rules.json, add samples to the tuning split in gen-corpus.mjs, and see what the tuning split misses with npm run injection:bench -- --misses tuning. The script prints no text from any held-out set.
Limits
Section titled “Limits”- Heuristics can be evaded: a novel paraphrase, an unusual encoding, or an injection split across several tool results may not be recognised.
- Indirect-injection coverage in coding agents depends on each vendor’s hook API, as the table above shows.
- A security discussion that quotes an attack (for example a blog post about “ignore previous instructions”) is detected as the attack it quotes. These are most of the false positives measured above.
- Most attacks that avoid the published wording are not detected: about 70% of the attacks in the public datasets above. Use warn mode to see what the rules catch in your own traffic, and do not rely on them alone.
- Prompts in languages other than English are covered only for the override phrasings in the rules (French, German, Spanish, Italian, Portuguese, Russian and Chinese).
- Content longer than 200,000 characters is checked up to that length.
- The gateway checks third-party tool descriptions; the descriptions of Shield360’s own document and context tools are not checked.