agentsec · Tools · toolpoison

Offline · stdlib-only · MIT

toolpoison: detect hidden instructions and rug-pulls in MCP manifests in AI agents


toolpoison is a static, offline scanner for MCP (Model Context Protocol) tool manifests. It reads the JSON a server returns from tools/list and flags patterns associated with tool poisoning and prompt injection: hidden instructions in descriptions, invisible Unicode, references to secrets, exfiltration hints, data-smuggling parameters, cross-tool shadowing, confusable names, duplicates, and hidden markup.

What it detects

toolpoison is a static, offline scanner for MCP (Model Context Protocol) tool manifests. It reads the JSON a server returns from tools/list and flags patterns associated with tool poisoning and prompt injection: hidden instructions in descriptions, invisible Unicode, references to secrets, exfiltration hints, data-smuggling parameters, cross-tool shadowing, confusable names, duplicates, and hidden markup.

Honesty notice. toolpoison uses heuristic pattern matching. A clean result does not prove a server is safe. It never runs or connects to a server, so it cannot see runtime behavior (what the tool actually does with your data), tool *outputs*, or descriptions that change after you approved them ("rug pulls"). The only way it can catch a rug pull is if you save snapshots and compare them with --diff. Expect false positives and false negatives. A determined attacker can phrase instructions in ways no regex anticipates.

Rules

IDNameDefault severityWhat it flags
TP01hidden-instructionhigh"ignore previous instructions", "do not tell the user", <IMPORTANT>/<system> tags, "before using this tool, read…", "otherwise the tool will not work"
TP02invisible-unicodehigh / criticalZero-width chars, BOM, soft hyphen, variation selectors, private-use chars, and other format/control chars (high). Bidi overrides and Unicode tag characters (critical). Tag text is decoded and shown
TP03sensitive-pathhigh~/.ssh, id_rsa, .env, ~/.aws/credentials, mcp.json, claude_desktop_config.json, ~/.cursor/, kube/docker/npm/pypi/netrc/git credentials, /etc/passwd, shell history, private keys/seed phrases
TP04exfiltration-hinthigh (medium for base64-only)"send … to https://…", curl/wget to URLs, "include the contents in the X parameter", base64-encoding instructions, known request-catcher domains, hard-coded BCC/CC addresses
TP05smuggling-parametermedium (high / low)Parameter names such as sidenote, notes, context, metadata, feedback, reasoning. Escalates to high if the parameter's description asks for file contents, keys, credentials, conversation history, and similar. Drops to low if the parameter has no description
TP06cross-tool-shadowinghigh"when the send_email tool is used…", "instead of the X tool", "all other tools must…", "always BCC/redirect…"
TP07excessive-lengthlowDescriptions longer than --max-desc-len (default 1000 chars)
TP08confusable-namemedium (high if mixed scripts)Tool or parameter names that mix scripts (for example, a Cyrillic а inside read_file) or contain non-ASCII letters
TP09duplicate-namemediumThe same tool name (NFKC + case-folded) defined more than once, within or across scanned files
TP10hidden-markupmediumHTML comments <!-- -->, display:none/hidden elements, <details>, markdown comments [//]: #, script/style/iframe tags, long runs of blank lines or padding that push text out of view
TP11annotation-deceptionhighreadOnlyHint / destructiveHint / title annotations that contradict a destructive name, description, or schema
TP12hidden-schema-defaulthighHidden or unusual schema defaults on exfil-shaped parameters (bcc, webhook, callback, and similar)
TP13preference-manipulationhighDescription steers the model to prefer this tool, levy an extra fee, or always invoke a follow-up tool
TP14elicitation-credential-phishhighTool text or server-level instructions / serverInstructions tell the model to use MCP elicitation to harvest a password, token, or other secret

Usage

# Scan one or more manifests
python3 toolpoison.py server-a.json server-b.json

# Read from stdin (for example, a saved tools/list response)
cat tools.json | python3 toolpoison.py -

# Machine-readable output, failing only on high/critical
python3 toolpoison.py --json --min-severity high tools.json

# Rug-pull check: compare an approved snapshot with the current one (also scans NEW)
python3 toolpoison.py --diff approved.json current.json

python3 toolpoison.py --list-rules

After pip install of the kit, the same options work as agentsec toolpoison …. Every tool also accepts --help and almost every tool accepts --json.

Example output

These are real runs against the files in examples/.

python3 toolpoison.py clean.json (exit code 0):

python3 toolpoison.py poisoned.json --min-severity high (exit code 1):

python3 toolpoison.py --diff clean.json clean_v2_rugpull.json (exit code 1):

Scanned 2 tool(s) in 1 file(s): no findings (min severity: low).
Note: toolpoison is heuristic pattern matching. A clean result does not prove a server is safe; it cannot see runtime behaviour or descriptions that change after approval (rug pulls) unless you diff snapshots (--diff).

SARIF and CI

agentsec toolpoison path/to/input --json > toolpoison.json
# toolpoison does not yet emit SARIF; use --json and keep --fail-on for CI.

# GitHub Actions (composite action ships with the repo)
- uses: dunkleschitz/agentsec@v0
  with:
    tools: toolpoison
    paths: path/to/input
    fail-on: high

--sarif is not available on this tool yet (resultguard and toolpoison are reserved as good first issues). Use --json and a --fail-on exit gate instead.

OWASP mapping

Heuristic mapping of what this tool helps a reviewer look for. Not an official OWASP endorsement, and not complete coverage of any item. Sources: OWASP Top 10 for LLM Applications 2025 and OWASP Top 10 for Agentic Applications 2026.

Known limits

Full source, tests, and fixtures: tools/toolpoison/ in the repository. Install the kit with pip install git+https://github.com/dunkleschitz/agentsec.git.