tool-poisoning
No tool-poisoning indicators in tool metadata
The sections below are generated from the CLI (methodology 0.3-draft, CLI 0.3.0); the same text is printed by sealmark explain tool-poisoning.
- pack
core- weight
- critical
- scope
- server (what it declares, not what it does)
- decided by
- static
- since
- 0.1-draft (methodology 0.3-draft)
- config keys
checks.tool-poisoning.enabledchecks.tool-poisoning.severity
Probes
hidden-charactersinvisible and bidirectional control characters in any metadata string corectl-strongESC (U+001B), NUL (U+0000) or a C1 control (U+0080 to U+009F) in any metadata string corectl-weakanother C0 control (U+0001 to U+0008, U+000B, U+000C, U+000E to U+001A, U+001C to U+001F) or DEL (U+007F); tab, line feed and carriage return are allowedtells the model to ignore prior instructionsignore (all |any )?(the |your )?(previous|prior|above|earlier) (instructions|prompts?|rules) coreuses an <IMPORTANT> block aimed at the model<\s*important\s*> coretells the model to read or send a credential file\b(?:you (?:must|should|need to|have to)|first|then|also|always|please|before\b[^.;]{0,40}?)\s+(?:read|cat|open|send|upload|post|forward|attach|include|copy)\b[^.;]{0,60}?(?:~\/\.ssh|\.ssh\/id_|\bid_(?:rsa|ed25519|ecdsa|dsa)\b|\.aws\/credentials|\/etc\/(?:passwd|shadow)|\.netrc|\.git-credentials) coreasks the model to send a credential file to a URL\b(?:send|post|upload|forward|transmit|leak)\b[^.;]{0,60}?(?:~\/\.ssh|\.ssh\/id_|\bid_(?:rsa|ed25519|ecdsa|dsa)\b|\.aws\/credentials|\/etc\/(?:passwd|shadow)|\.netrc|\.git-credentials)[^;]{0,80}?https?:\/\/ corementions a credential file path(?:~\/\.ssh|\.ssh\/id_|\bid_(?:rsa|ed25519|ecdsa|dsa)\b|\.aws\/credentials|\/etc\/(?:passwd|shadow)|\.netrc|\.git-credentials)asks the model to send secrets or conversation data to a URL\b(send|post|upload|forward|transmit|leak)\b[^.;]{0,80}\b(credentials?|secrets?|passwords?|api[ _-]?keys?|tokens?|private keys?|ssh|conversation|chat history|system prompt|\.env)\b[^.;]{0,80}https?:\/\/ corementions exfiltration\bexfiltrat coreasks the model to read a .env file and pass it on\b(read|cat|open|include|attach|send|upload|copy)\b[^.]{0,40}\.env\b coretells the model to hide something from the user(do not|don't|never) (tell|inform|mention|show|reveal|let)[^.]{0,40}\buser\breferences the system promptsystem promptmentions a .env file\.env\buses a <system> or <instructions> tag<\s*(system|instructions?)\s*>asks the model to call another tool firstbefore (using|calling|running) this tool[^.]{0,40}(call|use|read|run)overrides prior guidance\b(overrid(?:e|es|ing)|supersed(?:e|es)|takes? precedence over)\b[^.]{0,40}\b(instructions|guidance|rules|prompt)asks the model to send user data to a URL\b(send|post|upload|forward)\b[^.]{0,40}\b(user'?s|all|every)\b[^.]{0,30}\b(data|files?|messages?|conversation|history|context)\b[^.]{0,60}https?:\/\/server-instructionsthe `instructions` string from initialize is scanned by every rule above (no length rule: long instructions are ordinary) coreserver-infoserverInfo name, title, description and websiteUrl are scanned by every rule above core
core marks the canonical probes of the check; the others are extra coverage. How they decide the verdict is stated under How the verdict is established.
Settings and thresholds
Every check also takes checks.tool-poisoning.enabled and checks.tool-poisoning.severity; they are described once in the configuration reference.
This check has no numeric threshold.
How the verdict is established
- Scoring. critical and scored: pass = 1 point, part = 0.5, fail = 0. A fail caps the pack's grade at D, whatever the other checks scored.
What to fix
Remove hidden characters and any text that addresses the model ("ignore previous instructions", <IMPORTANT> blocks, instructions to read and send keys or files) from tool names, descriptions, schema text, prompts, resources, the server instructions and serverInfo (name, title, description, website); describe what the tool does and nothing else. The server instructions are model-visible text like a tool description: long instructions are fine, an injection or a hidden character in them is not.
False positives
A server that embeds ANSI colour codes in its descriptions on purpose fails by design: ESC drives a terminal that renders the text. Only phrases with no innocent reading are strong and fail the check; a careful honest description can only earn part. Documentation that names a key path is weak, an instruction to read and send it is strong.
False negatives
Static text matching. A description or instruction that carries an injection in words the patterns do not know passes. Tool names, descriptions, schema text, the server instructions (first 1 MiB), serverInfo, prompts and resources are scanned; tool output is not (see notCovered).
Notes
This check reads the text a server hands to a model and looks for the phrases and hidden characters that tool-poisoning attacks use. It is a lint over declared metadata. It does not look at what a tool returns when called.
What is scanned#
No tool is called. Every string in every tool definition is scanned, to a nesting depth of 8: name, title, description, all text inside the inputSchema (parameter descriptions, enum values, defaults) and any other string the tool object carries. The name, title, description and uri of every prompt and resource the server lists are scanned with the same rules. Matching is case-insensitive. The patterns themselves are in the Probes list above.
Each evidence line says which tool and which field matched, whether the match was strong or weak, and quotes up to 80 characters of the matched text. If the server declares no tools the check is n/a; if the tools/list reply could not be parsed it is inconclusive (see protocol).
Strong and weak indicators#
The Probes list does not say which tier a pattern is in. Each has a separate effect:
- Strong gives
fail: the instruction to ignore prior instructions, the<IMPORTANT>block, the three instructions to read or send a credential file, secrets or conversation data to a URL,exfiltrat, the instruction to read a.envfile and pass it on, hidden or bidirectional control characters, and ESC, NUL or a C1 control. They have almost no legitimate reason to appear in tool metadata. - Weak gives
part, when no strong indicator is present: every other pattern in the list, and a description longer than 4,000 characters (hidden instructions hide in long text). Honest text can contain them, so they are flagged for a human.
Details the pattern text does not carry:
- The credential paths are
~/.ssh,.ssh/id_,id_rsa,id_ed25519,id_ecdsa,id_dsa,.aws/credentials,/etc/passwd,/etc/shadow,.netrcand.git-credentials. A path with no read or send instruction around it is weak. - Before the secrets-to-a-URL pattern matches, phrases that say how the tool authenticates are removed (
using,withorvia, an optionalyour,theora, up to two words, then a secret noun), so "upload the file using your API token; see https://docs.example.test" does not match. - Hidden characters are strong for: U+200B; U+202A to U+202E, U+2060 to U+2064 and U+2066 to U+2069; the Unicode tag block U+E0000 to U+E007F; a run of three or more of U+200B to U+200F, U+2060 and U+FEFF; and a U+FEFF that is not first. The only tag-block exception is the three exact tag sequences of the England, Scotland and Wales flag emoji, because any other tag run can carry hidden ASCII.
- One or two of U+200C to U+200F, or a U+FEFF at the very start, is weak: these are ordinary in emoji sequences and right-to-left text.
- Other C0 controls and DEL are weak, because they are usually accidents such as pasted binary text. Tab, line feed and carriage return are never flagged, so CRLF line ends are clean.
Thresholds#
| Verdict | When |
|---|---|
| fail | at least one strong indicator |
| partial | weak indicators only |
| pass | no indicator matched |
| n/a | the server declares no tools |
| inconclusive | the tool listing could not be parsed |
Why two tiers: one benign sentence, such as a safety note that mentions the system prompt or a description that mentions .env, must not cap a real server at D. Strong indicators have almost no honest use; weak ones cost half a point and are shown to a human.
Limits#
- A pass means none of the patterns matched. Paraphrased or non-English injections, and instructions split across fields, pass.
- The check looks for characters, not for what a model would do with the text, and nothing about behaviour at call time or injection through tool output (see the known blind spots).
- A description that legitimately documents reading a
.envfile or a path such as~/.ssh/id_rsa, or a security tool that quotes an attack phrase it detects, can still match.