tool-poisoning

No tool-poisoning indicators in tool metadata

shipped

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-characters invisible and bidirectional control characters in any metadata string core
  • ctl-strong ESC (U+001B), NUL (U+0000) or a C1 control (U+0080 to U+009F) in any metadata string core
  • ctl-weak another 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 allowed
  • tells the model to ignore prior instructions ignore (all |any )?(the |your )?(previous|prior|above|earlier) (instructions|prompts?|rules) core
  • uses an <IMPORTANT> block aimed at the model <\s*important\s*> core
  • tells 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) core
  • asks 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?:\/\/ core
  • mentions 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?:\/\/ core
  • mentions exfiltration \bexfiltrat core
  • asks 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 core
  • tells the model to hide something from the user (do not|don't|never) (tell|inform|mention|show|reveal|let)[^.]{0,40}\buser\b
  • references the system prompt system prompt
  • mentions a .env file \.env\b
  • uses a <system> or <instructions> tag <\s*(system|instructions?)\s*>
  • asks the model to call another tool first before (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-instructions the `instructions` string from initialize is scanned by every rule above (no length rule: long instructions are ordinary) core
  • server-info serverInfo 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 .env file 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, .netrc and .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, with or via, an optional your, the or a, 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#

VerdictWhen
failat least one strong indicator
partialweak indicators only
passno indicator matched
n/athe server declares no tools
inconclusivethe 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 .env file or a path such as ~/.ssh/id_rsa, or a security tool that quotes an attack phrase it detects, can still match.