agent-threat-rules 4.1.5 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +688 -1
- package/README.md +355 -125
- package/dist/adapters/openshell-filter.d.ts +10 -3
- package/dist/adapters/openshell-filter.d.ts.map +1 -1
- package/dist/adapters/openshell-filter.js +10 -3
- package/dist/adapters/openshell-filter.js.map +1 -1
- package/dist/adapters/stdio-adapter.js +3 -3
- package/dist/adapters/stdio-adapter.js.map +1 -1
- package/dist/cli/mcp-tools-document.d.ts +40 -0
- package/dist/cli/mcp-tools-document.d.ts.map +1 -0
- package/dist/cli/mcp-tools-document.js +95 -0
- package/dist/cli/mcp-tools-document.js.map +1 -0
- package/dist/cli/scan-handler.d.ts +8 -0
- package/dist/cli/scan-handler.d.ts.map +1 -1
- package/dist/cli/scan-handler.js +126 -34
- package/dist/cli/scan-handler.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +80 -43
- package/dist/cli.js.map +1 -1
- package/dist/converters/sarif.d.ts +14 -2
- package/dist/converters/sarif.d.ts.map +1 -1
- package/dist/converters/sarif.js +7 -9
- package/dist/converters/sarif.js.map +1 -1
- package/dist/corpus-event.d.ts +47 -5
- package/dist/corpus-event.d.ts.map +1 -1
- package/dist/corpus-event.js +68 -29
- package/dist/corpus-event.js.map +1 -1
- package/dist/embedding/model-loader.d.ts +2 -0
- package/dist/embedding/model-loader.d.ts.map +1 -1
- package/dist/embedding/model-loader.js +3 -1
- package/dist/embedding/model-loader.js.map +1 -1
- package/dist/enforcement.d.ts +8 -0
- package/dist/enforcement.d.ts.map +1 -1
- package/dist/enforcement.js +10 -0
- package/dist/enforcement.js.map +1 -1
- package/dist/engine.d.ts +150 -6
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +337 -76
- package/dist/engine.js.map +1 -1
- package/dist/eval/embedding-provenance.d.ts +40 -0
- package/dist/eval/embedding-provenance.d.ts.map +1 -0
- package/dist/eval/embedding-provenance.js +71 -0
- package/dist/eval/embedding-provenance.js.map +1 -0
- package/dist/eval/eval-harness.d.ts +34 -0
- package/dist/eval/eval-harness.d.ts.map +1 -1
- package/dist/eval/eval-harness.js +49 -2
- package/dist/eval/eval-harness.js.map +1 -1
- package/dist/eval/hackaprompt-corpus.d.ts +12 -0
- package/dist/eval/hackaprompt-corpus.d.ts.map +1 -1
- package/dist/eval/hackaprompt-corpus.js +15 -1
- package/dist/eval/hackaprompt-corpus.js.map +1 -1
- package/dist/eval/hackaprompt-provenance.d.ts +52 -0
- package/dist/eval/hackaprompt-provenance.d.ts.map +1 -0
- package/dist/eval/hackaprompt-provenance.js +108 -0
- package/dist/eval/hackaprompt-provenance.js.map +1 -0
- package/dist/eval/run-hackaprompt-benchmark.d.ts +18 -8
- package/dist/eval/run-hackaprompt-benchmark.d.ts.map +1 -1
- package/dist/eval/run-hackaprompt-benchmark.js +154 -64
- package/dist/eval/run-hackaprompt-benchmark.js.map +1 -1
- package/dist/eval/run-pint-benchmark.js +11 -1
- package/dist/eval/run-pint-benchmark.js.map +1 -1
- package/dist/eval/skill-benchmark.d.ts +26 -6
- package/dist/eval/skill-benchmark.d.ts.map +1 -1
- package/dist/eval/skill-benchmark.js +49 -16
- package/dist/eval/skill-benchmark.js.map +1 -1
- package/dist/flywheel.d.ts +4 -0
- package/dist/flywheel.d.ts.map +1 -1
- package/dist/flywheel.js +12 -4
- package/dist/flywheel.js.map +1 -1
- package/dist/hook-handler.d.ts +11 -1
- package/dist/hook-handler.d.ts.map +1 -1
- package/dist/hook-handler.js +84 -16
- package/dist/hook-handler.js.map +1 -1
- package/dist/hook-tool-output.d.ts +47 -0
- package/dist/hook-tool-output.d.ts.map +1 -0
- package/dist/hook-tool-output.js +110 -0
- package/dist/hook-tool-output.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/layer-integration.d.ts +4 -2
- package/dist/layer-integration.d.ts.map +1 -1
- package/dist/layer-integration.js +6 -2
- package/dist/layer-integration.js.map +1 -1
- package/dist/mcp-server.d.ts +8 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +1 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/scan.d.ts.map +1 -1
- package/dist/mcp-tools/scan.js +9 -1
- package/dist/mcp-tools/scan.js.map +1 -1
- package/dist/measurement/enforce-lane.d.ts +66 -0
- package/dist/measurement/enforce-lane.d.ts.map +1 -0
- package/dist/measurement/enforce-lane.js +89 -0
- package/dist/measurement/enforce-lane.js.map +1 -0
- package/dist/measurement/from-eval-harness.d.ts +16 -3
- package/dist/measurement/from-eval-harness.d.ts.map +1 -1
- package/dist/measurement/from-eval-harness.js +15 -5
- package/dist/measurement/from-eval-harness.js.map +1 -1
- package/dist/measurement/in-sample-ablation.d.ts +29 -0
- package/dist/measurement/in-sample-ablation.d.ts.map +1 -0
- package/dist/measurement/in-sample-ablation.js +45 -0
- package/dist/measurement/in-sample-ablation.js.map +1 -0
- package/dist/measurement/schema.d.ts +38 -1
- package/dist/measurement/schema.d.ts.map +1 -1
- package/dist/measurement/schema.js +43 -1
- package/dist/measurement/schema.js.map +1 -1
- package/dist/quality/action-eligibility.d.ts +4 -2
- package/dist/quality/action-eligibility.d.ts.map +1 -1
- package/dist/quality/action-eligibility.js.map +1 -1
- package/dist/rule-confidence.d.ts +31 -0
- package/dist/rule-confidence.d.ts.map +1 -0
- package/dist/rule-confidence.js +49 -0
- package/dist/rule-confidence.js.map +1 -0
- package/dist/rule-scaffolder.d.ts.map +1 -1
- package/dist/rule-scaffolder.js +43 -4
- package/dist/rule-scaffolder.js.map +1 -1
- package/dist/semantic-evaluator.d.ts.map +1 -1
- package/dist/semantic-evaluator.js +15 -13
- package/dist/semantic-evaluator.js.map +1 -1
- package/dist/shadow-evaluator.d.ts +7 -2
- package/dist/shadow-evaluator.d.ts.map +1 -1
- package/dist/shadow-evaluator.js +29 -25
- package/dist/shadow-evaluator.js.map +1 -1
- package/dist/testcase-event.d.ts +82 -1
- package/dist/testcase-event.d.ts.map +1 -1
- package/dist/testcase-event.js +135 -16
- package/dist/testcase-event.js.map +1 -1
- package/dist/tier0-invariant.d.ts.map +1 -1
- package/dist/tier0-invariant.js +1 -0
- package/dist/tier0-invariant.js.map +1 -1
- package/dist/tier1-blacklist.d.ts.map +1 -1
- package/dist/tier1-blacklist.js +1 -0
- package/dist/tier1-blacklist.js.map +1 -1
- package/dist/tool-definition-scope.d.ts +45 -0
- package/dist/tool-definition-scope.d.ts.map +1 -0
- package/dist/tool-definition-scope.js +68 -0
- package/dist/tool-definition-scope.js.map +1 -0
- package/dist/types.d.ts +42 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/verdict.d.ts +46 -2
- package/dist/verdict.d.ts.map +1 -1
- package/dist/verdict.js +99 -14
- package/dist/verdict.js.map +1 -1
- package/package.json +2 -1
- package/rules/agent-manipulation/ATR-2026-00030-cross-agent-attack.yaml +2 -2
- package/rules/agent-manipulation/ATR-2026-00139-casual-authority-redirect.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00328-named-mode-persona-override.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00330-yoja-allcaps-settings-block-persona.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00418-weknora-mcp-config-rce.yaml +5 -5
- package/rules/agent-manipulation/ATR-2026-00432-superagi-output-handler-eval-rce.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00440-semantic-kernel-vector-store-eval-rce.yaml +3 -3
- package/rules/context-exfiltration/ATR-2026-00426-nl-output-injection-credential-leak.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00702-ipi-credential-exfil-via-agent-action.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00707-ipi-file-exfil-with-destroy-or-tamper.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00851-indirect-pi-financial-payment-exfil.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01457-sysprompt-completion-clone-attack.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01462-sysprompt-override-pseudo-admin.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01463-imend-imsystem-prompt-injection-format.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01606-ssrf-internal-network-scan.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02847-policy-justified-exfil-to-email.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02855-cross-email-exfil-relay.yaml +79 -0
- package/rules/context-exfiltration/ATR-2026-02861-exfil-send-email-with-body-directive.yaml +83 -0
- package/rules/context-exfiltration/ATR-2026-02866-idna-label-separator-in-fetch-url-host.yaml +104 -0
- package/rules/excessive-autonomy/ATR-2026-00710-ipi-physical-access-grant.yaml +1 -1
- package/rules/model-abuse/ATR-2026-01897-host-sabotage.yaml +2 -2
- package/rules/model-security/ATR-2026-00433-modelcache-torch-load-deserialization-rce.yaml +1 -1
- package/rules/privilege-escalation/ATR-2026-00436-enclave-vm-sandbox-escape-rce.yaml +1 -1
- package/rules/privilege-escalation/ATR-2026-01601-sql-injection-destructive-ddl.yaml +2 -2
- package/rules/privilege-escalation/ATR-2026-01612-debug-mode-privilege-escalation.yaml +1 -1
- package/rules/privilege-escalation/ATR-2026-02867-identifier-typed-tool-argument-dot-dot-path-segment.yaml +106 -0
- package/rules/prompt-injection/ATR-2026-00004-system-prompt-override.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00080-encoding-evasion.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00086-visual-spoofing.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00203-context-pollution-skill-description.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00228-structured-jailbreak.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00233-structured-jailbreak.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00256-base-n-encoding-jailbreak.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00264-latent-injection-translation.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00265-latent-injection-rag-document.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00282-perez-prompt-injection-hijack.yaml +4 -4
- package/rules/prompt-injection/ATR-2026-00321-hyphenated-system-instruction-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00394-backspace-deletion-character-injection.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00446-variable-assignment-payload-injection.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00476-garak-web-injection-javascript-alert-1.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00481-garak-dan-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00489-garak-encoding-javascript-alert-1.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00508-escape-delimiter-wrapped-goal-hijacking-in-user-input-prompt.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00512-rules-file-backdoor-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01005-many-shot-repetition-override.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01006-payload-split-string-concat-injection.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01015-evasion-refusal-suppression.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01016-evasion-response-priming-forced-opener.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01026-evasion-unicode-glitch-token-anomaly.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01304-indirect-injection-carrier-important-override.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01759-dos-new-important-instructions-injection-in-document.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01865-llmail-fake-email-boundary-marker.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02010-emoji-only-payload.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-02815-injected-halt-order-canned-reply-dos.yaml +80 -0
- package/rules/prompt-injection/ATR-2026-02820-tool-result-orders-agent-to-hide-its-own-action.yaml +106 -0
- package/rules/prompt-injection/ATR-2026-02858-context-suppression-directive.yaml +73 -0
- package/rules/prompt-injection/ATR-2026-02859-tool-output-agent-role-reassignment.yaml +71 -0
- package/rules/prompt-injection/ATR-2026-02860-forged-instruction-boundary-marker.yaml +70 -0
- package/rules/prompt-injection/ATR-2026-02862-fake-chat-template-turn-injection.yaml +79 -0
- package/rules/skill-compromise/ATR-2026-00060-skill-impersonation.yaml +1 -1
- package/rules/skill-compromise/ATR-2026-00063-skill-chain-attack.yaml +2 -2
- package/rules/skill-compromise/ATR-2026-00066-parameter-injection.yaml +2 -2
- package/rules/skill-compromise/ATR-2026-00149-skill-exfil-compound.yaml +6 -6
- package/rules/skill-compromise/ATR-2026-00223-reverse-shell-dropper.yaml +2 -2
- package/rules/skill-compromise/ATR-2026-01755-backdoor-pot-linguistic-trigger-phrase.yaml +1 -1
- package/rules/skill-compromise/ATR-2026-01756-backdoor-pot-symbol-emoticon-trigger.yaml +1 -1
- package/rules/skill-compromise/ATR-2026-02261-code-credential-exfil-chain.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-00012-unauthorized-tool-call.yaml +23 -23
- package/rules/tool-poisoning/ATR-2026-00013-tool-ssrf.yaml +16 -5
- package/rules/tool-poisoning/ATR-2026-00161-important-tag-cross-tool-shadowing.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-00277-echo-template-command-injection.yaml +2 -2
- package/rules/tool-poisoning/ATR-2026-00494-garak-exploitation-mixedunassigned.yaml +4 -4
- package/rules/tool-poisoning/ATR-2026-00581-mcp-tool-rug-pull-post-approval-redefinition.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-01303-tool-schema-enumeration-social-engineering.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-01307-mcp-dns-rebinding-attack.yaml +2 -2
- package/rules/tool-poisoning/ATR-2026-01775-semantic-mcp-tool-manifest-poisoning.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-01932-shadow-undeclared-mcp-server-registration.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-02025-mcp-full-schema-poisoning-nondescription-field.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-02817-untrusted-content-dictates-tool-arguments.yaml +83 -0
- package/rules/tool-poisoning/ATR-2026-02864-non-canonical-ipv4-host-in-url-tool-argument.yaml +102 -0
- package/rules/tool-poisoning/ATR-2026-02865-read-only-utility-write-flag-in-command-argument.yaml +157 -0
- package/rules/tool-poisoning/ATR-2026-02868-ssrf-ipv6-transition-address-embedded-private-ipv4.yaml +107 -0
- package/spec/README.md +6 -3
- package/spec/atr-event-v1.0.md +13 -8
- package/spec/atr-method-v1.1.md +2 -2
- package/spec/category-registry/gemara/README.md +102 -0
- package/spec/category-registry/gemara/ai-agent-capability-catalog.yaml +157 -0
- package/spec/category-registry/gemara/atr-attack-vector-catalog.yaml +130 -0
- package/spec/category-registry/gemara/atr-detection-control-catalog.yaml +327 -0
- package/spec/category-registry/gemara/atr-threat-catalog.yaml +246 -0
- package/spec/category-registry/gemara/src/editorial.yaml +743 -0
- package/spec/mappings/README.md +1 -1
- package/spec/mappings/atr-to-nist-csf-2.0.md +5 -4
- package/spec/schema/event.schema.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
title: Fake Chat-Template Turn Injection Detection
|
|
2
|
+
id: ATR-2026-02862
|
|
3
|
+
rule_version: 1
|
|
4
|
+
status: experimental
|
|
5
|
+
description: |
|
|
6
|
+
Detects untrusted content (most often tool output: a fetched email, document or page) that places two chat-control tokens next to each other, such as <|end|><|user|>, <|im_end|><|im_start|> or <im end><im start>, to fake the end of one conversation turn and the start of another and so smuggle a new "user" or "system" instruction into the model's context. Scope is deliberately narrow: only an adjacent pair of pipe-delimited role tokens, or the pipe-less "im start"/"im end" spelling, is matched. A single token on its own (for example <|im_start|>user), Llama-3 header tokens, <|endoftext|>, and bare XML-style role tags such as <system><user> are out of scope. Related rules divide the space: ATR-2026-01463 covers <|im_start|>system and <|system|> role spoofing, ATR-2026-00395 covers <|im_end|> in user input, and ATR-2026-01860 covers spaced variants such as <|end user|>. The rule was mined from LLMail-Inject submissions and matches none of the other attack corpora in the repository.
|
|
7
|
+
author: ATR Community (via LLMail-Inject benchmark)
|
|
8
|
+
date: 2026/10/15
|
|
9
|
+
schema_version: '0.1'
|
|
10
|
+
detection_tier: pattern
|
|
11
|
+
maturity: test
|
|
12
|
+
severity: high
|
|
13
|
+
references:
|
|
14
|
+
owasp_llm:
|
|
15
|
+
- LLM01:2025
|
|
16
|
+
owasp_agentic:
|
|
17
|
+
- ASI01:2026
|
|
18
|
+
mitre_atlas:
|
|
19
|
+
- AML.T0051 - LLM Prompt Injection
|
|
20
|
+
- AML.T0051.001 - Indirect
|
|
21
|
+
research:
|
|
22
|
+
- https://github.com/microsoft/llmail-inject-challenge
|
|
23
|
+
compliance:
|
|
24
|
+
eu_ai_act:
|
|
25
|
+
- article: '15'
|
|
26
|
+
context: Article 15 (accuracy, robustness and cybersecurity) calls for resilience against attempts to alter an AI system's use or behaviour by exploiting its vulnerabilities; this rule provides detection evidence supporting that requirement for forged turn boundaries in untrusted content.
|
|
27
|
+
strength: primary
|
|
28
|
+
- article: '9'
|
|
29
|
+
context: Article 9 (risk management system) expects identified risks to be monitored; this rule provides detection evidence supporting the monitoring of indirect prompt injection through forged chat-template tokens.
|
|
30
|
+
strength: secondary
|
|
31
|
+
nist_ai_rmf:
|
|
32
|
+
- subcategory: MS.2.7
|
|
33
|
+
context: NIST AI RMF MEASURE 2.7 (security and resilience evaluated and documented) is supported by this rule's runtime detection of forged chat-template turn boundaries.
|
|
34
|
+
strength: primary
|
|
35
|
+
- subcategory: MG.2.3
|
|
36
|
+
context: NIST AI RMF MANAGE 2.3 (procedures respond to previously unknown identified risks) is supported by detections that surface new turn-forging spellings for response.
|
|
37
|
+
strength: secondary
|
|
38
|
+
iso_42001:
|
|
39
|
+
- clause: '8.1'
|
|
40
|
+
context: 'Clause 8.1 (operational planning and control): runtime detection of forged chat-control tokens is an operational control on what untrusted content may do to the conversation structure.'
|
|
41
|
+
strength: primary
|
|
42
|
+
- clause: '6.2'
|
|
43
|
+
context: 'Clause 6.2 (AI objectives and planning to achieve them): detections from this rule give measurable input to objectives for resisting indirect prompt injection.'
|
|
44
|
+
strength: secondary
|
|
45
|
+
tags:
|
|
46
|
+
category: prompt-injection
|
|
47
|
+
subcategory: chat-template-turn-injection
|
|
48
|
+
scan_target: mcp
|
|
49
|
+
confidence: high
|
|
50
|
+
agent_source:
|
|
51
|
+
type: llm_io
|
|
52
|
+
framework:
|
|
53
|
+
- openai
|
|
54
|
+
- anthropic
|
|
55
|
+
- langchain
|
|
56
|
+
- llamaindex
|
|
57
|
+
- autogen
|
|
58
|
+
- crewai
|
|
59
|
+
provider:
|
|
60
|
+
- any
|
|
61
|
+
detection:
|
|
62
|
+
conditions:
|
|
63
|
+
- field: content
|
|
64
|
+
operator: regex
|
|
65
|
+
value: (?i)(?:<\|\s*(?:im[_ ]?start|im[_ ]?end|end|system|assistant|user)\s*\|?>|<\s*im[_ ]?(?:start|end)\s*\|?>)\s*(?:<\|\s*(?:im[_ ]?start|im[_ ]?end|end|system|assistant|user)\s*\|?>|<\s*im[_ ]?(?:start|end)\s*\|?>)
|
|
66
|
+
description: Fabricated adjacent chat-control tokens simulating a conversation turn boundary to smuggle a new instruction
|
|
67
|
+
condition: any
|
|
68
|
+
false_positives:
|
|
69
|
+
- Chat-template documentation, model cards, tokenizer tutorials or raw prompt logs that show a closing and an opening token next to each other, such as <|im_end|> followed by a real line break and <|im_start|>
|
|
70
|
+
- Fine-tuning datasets or exported transcripts in ChatML form that are returned by a tool as data rather than injected as instructions
|
|
71
|
+
- Security write-ups that quote a turn-forging payload verbatim
|
|
72
|
+
response:
|
|
73
|
+
actions:
|
|
74
|
+
- alert
|
|
75
|
+
- snapshot
|
|
76
|
+
- escalate
|
|
77
|
+
auto_response_threshold: high
|
|
78
|
+
message_template: '[ATR-2026-02862] Fake Chat-Template Turn Injection Detection detected.'
|
|
79
|
+
confidence: 85
|
|
@@ -24,7 +24,7 @@ references:
|
|
|
24
24
|
- ASI04:2026
|
|
25
25
|
mitre_atlas:
|
|
26
26
|
- AML.T0010 - AI Supply Chain Compromise
|
|
27
|
-
- AML.
|
|
27
|
+
- 'AML.T0115.002 - Publish Poisoned AI Artifacts: AI Agent Tools'
|
|
28
28
|
- AML.T0010.005 - AI Supply Chain Compromise of AI Agent Tool
|
|
29
29
|
mitre_attack:
|
|
30
30
|
- T1195 - Supply Chain Compromise
|
|
@@ -85,8 +85,8 @@ detection:
|
|
|
85
85
|
value: (?i)(send[_-]?(email|message|webhook|http|request)|upload|post[_-]?data|transmit|exfil)
|
|
86
86
|
- field: tool_args
|
|
87
87
|
operator: regex
|
|
88
|
-
value: (?i)(?=[\s\S]*(?<![a-z])\.env)(?=[\s\S]*(base64|btoa|encodeURI|hex\.encode|rot13|xor|compress|gzip|deflate|encrypt|webhook|ngrok|requestbin|pipedream|hookbin|burpcollaborator|interact\.sh|\boast\b|curl|wget|\bnc\b|exfil|upload))
|
|
89
|
-
description: Dotenv file access ONLY when chained with an obfuscation-or-egress indicator in the same args (multi-step access-then-obfuscate-then-egress chain). A bare startup-style dotenv reference no longer fires alone; the dangerous egress half is now required to flag this composite data-egress signal.
|
|
88
|
+
value: (?i)^(?=[\s\S]*(?<![a-z])\.env)(?=[\s\S]*(base64|btoa|encodeURI|hex\.encode|rot13|xor|compress|gzip|deflate|encrypt|webhook|ngrok|requestbin|pipedream|hookbin|burpcollaborator|interact\.sh|\boast\b|curl|wget|\bnc\b|exfil|upload))
|
|
89
|
+
description: 'Dotenv file access ONLY when chained with an obfuscation-or-egress indicator in the same args (multi-step access-then-obfuscate-then-egress chain). A bare startup-style dotenv reference no longer fires alone; the dangerous egress half is now required to flag this composite data-egress signal. Anchored with ^: the pattern is two lookaheads that each begin with [\s\S]*, so if both hold at some offset they also hold at offset 0, but unanchored the engine retried both scans from every start offset, which is quadratic in input length (each doubling took about four times as long: in V8 0.8s at 32K chars, 3.4s at 64K) and was 16% of all CPU in the benign-corpus FP scan. Anchored it is linear and returns the same verdict for every input.'
|
|
90
90
|
condition: any
|
|
91
91
|
false_positives:
|
|
92
92
|
- Legitimate backup or migration workflows that read, compress, and transfer files
|
|
@@ -73,13 +73,13 @@ detection:
|
|
|
73
73
|
value: (?i)(;|&&|\|\||`|\$\()\s*(whoami|\bid\b|uname\s+-a|curl\s+(?:-\S+\s+)*https?://(?!localhost|127\.)|wget\s+https?://(?!localhost|127\.)|\bnc\b\s|ncat\s|cat\s+/etc/(?:passwd|shadow|sudoers)|\b(?:ba)?sh\s+-[ic]|python[0-9]?\s+-c|base64\s+-d|rm\s+-[rf])
|
|
74
74
|
- field: tool_args
|
|
75
75
|
operator: regex
|
|
76
|
-
value: (?i)('\s*(OR|AND|UNION)\s+'
|
|
76
|
+
value: (?i)('\s*(OR|AND|UNION)\s+'|;\s*(DROP|DELETE|INSERT|UPDATE|ALTER|CREATE)\s|--\s*[;\"'\)\]]|/\*.*\*/)
|
|
77
77
|
- field: tool_args
|
|
78
78
|
operator: regex
|
|
79
79
|
value: (\.\./|\.\.\\){2,}|(\.\.(/|\\)){2,}(etc|windows|usr|home|root|var|tmp)
|
|
80
80
|
- field: tool_args
|
|
81
81
|
operator: regex
|
|
82
|
-
value: (?i)(\{\{.*\}\}|\$\{IFS|\$\{[^}]*[`(]|\$\{[^}]*jndi:|\$\([^)]*[A-Za-z]|<%.*%>|#\{.*\}|\{%.*%\})
|
|
82
|
+
value: (?i)((?:^|[^{])\{\{.*\}\}|\$\{IFS|\$\{[^}]*[`(]|\$\{[^}]*jndi:|\$\([^)]*[A-Za-z]|<%.*%>|#\{.*\}|\{%.*%\})
|
|
83
83
|
- field: tool_args
|
|
84
84
|
operator: regex
|
|
85
85
|
value: (?i)(\x00|%00|\\0|null\s*byte|\\u0000)
|
|
@@ -78,15 +78,15 @@ detection:
|
|
|
78
78
|
description: SSH directory archive — skills should not archive user SSH keys
|
|
79
79
|
- field: content
|
|
80
80
|
operator: regex
|
|
81
|
-
value: (?i)(find|ls|cat|strings)\s+[^\n]*(wallet\.dat|seed\.txt|mnemonic|keystore)[^\n]*(\||>|>>|curl|wget|fetch|POST)
|
|
81
|
+
value: (?i)(find|ls|cat|strings)\s+(?:\S[^\n]*)?(wallet\.dat|seed\.txt|mnemonic|keystore)[^\n]*(\||>|>>|curl|wget|fetch|POST)
|
|
82
82
|
description: Crypto wallet file enumeration and exfiltration
|
|
83
83
|
- field: content
|
|
84
84
|
operator: regex
|
|
85
|
-
value: (?i)sqlite3\s+[^\n]*(Cookies|Login\s*Data|cookies\.sqlite|places\.sqlite)
|
|
85
|
+
value: (?i)sqlite3\s+(?:\S[^\n]*)?(Cookies|Login\s*Data|cookies\.sqlite|places\.sqlite)
|
|
86
86
|
description: Browser cookie/credential database access
|
|
87
87
|
- field: content
|
|
88
88
|
operator: regex
|
|
89
|
-
value: (?i)(?:^|[\n;&|])\
|
|
89
|
+
value: (?i)(?:^|[\n;&|])[^\S\n]*(?:dig|nslookup|host)\b[^\n]{0,40}?\$[{(][^\n]{0,40}[.][a-z]
|
|
90
90
|
description: DNS exfiltration via encoded subdomain queries (shell var embedded in a queried hostname, command-position only)
|
|
91
91
|
- field: content
|
|
92
92
|
operator: regex
|
|
@@ -94,7 +94,7 @@ detection:
|
|
|
94
94
|
description: Cloud instance metadata service access (IMDS credential theft)
|
|
95
95
|
- field: content
|
|
96
96
|
operator: regex
|
|
97
|
-
value: (?i)cat\s+[^|\n]
|
|
97
|
+
value: (?i)cat\s+(?:[^\s|][^|\n]*)?\.(env|bashrc|zshrc|npmrc)[^|\n]*\|\s*(curl|wget|nc|python|base64)
|
|
98
98
|
description: Environment file bulk read piped to exfiltration
|
|
99
99
|
- field: content
|
|
100
100
|
operator: regex
|
|
@@ -106,11 +106,11 @@ detection:
|
|
|
106
106
|
description: Multi-file sensitive data collection in single cat command
|
|
107
107
|
- field: content
|
|
108
108
|
operator: regex
|
|
109
|
-
value: (?i)\[SYSTEM\]\s*[^\n]*(?:previous\s+instructions|new\s+directive|ignore\s+(?:all\s+)?previous|you\s+are\s+now)
|
|
109
|
+
value: (?i)\[SYSTEM\]\s*(?:\S[^\n]*)?(?:previous\s+instructions|new\s+directive|ignore\s+(?:all\s+)?previous|you\s+are\s+now)
|
|
110
110
|
description: Fake [SYSTEM] block attempting to override instructions
|
|
111
111
|
- field: content
|
|
112
112
|
operator: regex
|
|
113
|
-
value: (?i)
|
|
113
|
+
value: (?i)<!--(?![^>]*(?:https?://|!\[|\]\())\s*[A-Za-z0-9+/]{40,}={0,2}\s*-->
|
|
114
114
|
description: Base64-encoded hidden instruction in HTML comment — narrowed to require the comment be (essentially) JUST the blob. Previously any 40+ char alphanumeric run anywhere inside an HTML comment matched, which caught ordinary shields.io/markdown badge comments (e.g. '<!--  -->') whose long URL query string looks base64-shaped by character class alone.
|
|
115
115
|
- field: content
|
|
116
116
|
operator: regex
|
|
@@ -67,11 +67,11 @@ detection:
|
|
|
67
67
|
description: Base64 decode piped to shell execution
|
|
68
68
|
- field: content
|
|
69
69
|
operator: regex
|
|
70
|
-
value: curl\s+[^\n]
|
|
70
|
+
value: curl\s+(?:\S[^\n]*)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}[^|]*\|[^|]*(bash|sh)
|
|
71
71
|
description: Curl from raw IP address piped to shell
|
|
72
72
|
- field: content
|
|
73
73
|
operator: regex
|
|
74
|
-
value: '[A-Za-z0-9+/]{50
|
|
74
|
+
value: '[A-Za-z0-9+/]{50}=*\s*\|\s*base64\s+-D'
|
|
75
75
|
description: Long base64 encoded payload being decoded
|
|
76
76
|
- field: content
|
|
77
77
|
operator: regex
|
|
@@ -83,7 +83,7 @@ detection:
|
|
|
83
83
|
description: Variant of prism-of-insight trigger with synonym substitution
|
|
84
84
|
- field: user_input
|
|
85
85
|
operator: regex
|
|
86
|
-
value: (?i)\bconducting\s+(a|an)
|
|
86
|
+
value: (?i)\bconducting\s+(?:(a|an)\s*)?(comprehensive|thorough)\s+(elucidation|explication|exposition)\b
|
|
87
87
|
description: Variant of conducting-comprehensive-elucidation trigger (requires 'conducting' verb anchor)
|
|
88
88
|
condition: any
|
|
89
89
|
false_positives:
|
|
@@ -57,7 +57,7 @@ detection:
|
|
|
57
57
|
description: ASB canonical @_@ trigger — rare in organic text, specific POT artifact
|
|
58
58
|
- field: user_input
|
|
59
59
|
operator: regex
|
|
60
|
-
value: (?i)^.{15
|
|
60
|
+
value: (?i)^.{15}(?:.*\S)?[.!?]?\s+:(?:[)DoP])\s*(?:\.\s*)?$
|
|
61
61
|
description: Emoticon :) appended as terminal suffix to a task request (≥15 char task prefix)
|
|
62
62
|
condition: any
|
|
63
63
|
semantic:
|
|
@@ -55,7 +55,7 @@ detection:
|
|
|
55
55
|
conditions:
|
|
56
56
|
- field: content
|
|
57
57
|
operator: regex
|
|
58
|
-
value: (?i)(?:const\s+|let\s+|var\s+)?([A-Za-z_$][\w$]*)\s*=\s*(?:os\.environ(?:\.get)?\s*[\(\[]\s*['"][A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|PASSWD|CREDENTIAL|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|AUTH_?TOKEN)|process\.env\.[A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|CREDENTIAL|PRIVATE[_-]?KEY|ACCESS[_-]?KEY)|getenv\s*\(\s*['"][A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|CREDENTIAL)|(?:open|readFileSync|read_file)\s*\([^)\n]{0,60}(?:\.aws/credentials|\.ssh/id_[a-z0-9]+|\.netrc|credentials\.json))[\s\S]{0,160}?(?:base64\.(?:b64encode|standard_b64encode|encodebytes)\s*\(\s*\1\b|Buffer\.from\s*\(\s*\1\b[^)]*\)\.toString\s*\(\s*['"]base64|btoa\s*\(\s*\1\b|binascii\.(?:hexlify|b2a_base64)\s*\(\s*\1\b|\1\.encode\s*\(\s*['"]base64['"])[\s\S]{0,160}?(?:requests\.(?:post|put|patch)|httpx\.(?:post|put)|urllib\.request\.urlopen|urllib2\.urlopen|http\.client\.HTTPS?Connection|aiohttp\.[A-Za-z_]*\.(?:post|put)|fetch\s*\(|axios\.(?:post|put)|\.sendall\s*\(|\.sendto\s*\()
|
|
58
|
+
value: (?i)(?:^|[^\w$])(?:const\s+|let\s+|var\s+)?([A-Za-z_$][\w$]*)\s*=\s*(?:os\.environ(?:\.get)?\s*[\(\[]\s*['"][A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|PASSWD|CREDENTIAL|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|AUTH_?TOKEN)|process\.env\.[A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|CREDENTIAL|PRIVATE[_-]?KEY|ACCESS[_-]?KEY)|getenv\s*\(\s*['"][A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|CREDENTIAL)|(?:open|readFileSync|read_file)\s*\([^)\n]{0,60}(?:\.aws/credentials|\.ssh/id_[a-z0-9]+|\.netrc|credentials\.json))[\s\S]{0,160}?(?:base64\.(?:b64encode|standard_b64encode|encodebytes)\s*\(\s*\1\b|Buffer\.from\s*\(\s*\1\b[^)]*\)\.toString\s*\(\s*['"]base64|btoa\s*\(\s*\1\b|binascii\.(?:hexlify|b2a_base64)\s*\(\s*\1\b|\1\.encode\s*\(\s*['"]base64['"])[\s\S]{0,160}?(?:requests\.(?:post|put|patch)|httpx\.(?:post|put)|urllib\.request\.urlopen|urllib2\.urlopen|http\.client\.HTTPS?Connection|aiohttp\.[A-Za-z_]*\.(?:post|put)|fetch\s*\(|axios\.(?:post|put)|\.sendall\s*\(|\.sendto\s*\()
|
|
59
59
|
description: 'Ordered chain: secret read into a variable -> that SAME variable base64/hex-encoded (backreference-bound) -> outbound network send, within a bounded window. The backreference (\1) is what keeps the encode stage tied to the secret: a skill that reads a token for SDK auth but base64-encodes an UNRELATED image/file and POSTs it (a very common legitimate upload pattern) no longer matches, because the encoded operand is a different variable than the one holding the secret.'
|
|
60
60
|
condition: all
|
|
61
61
|
false_positives:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
title: Unauthorized Tool Call Detection
|
|
2
2
|
id: ATR-2026-00012
|
|
3
|
-
rule_version:
|
|
3
|
+
rule_version: 3
|
|
4
4
|
status: experimental
|
|
5
5
|
description: |
|
|
6
6
|
Detects unauthorized or malicious tool call attempts including parameter injection, path traversal, shell injection in string parameters, privilege escalation via parameter manipulation, tool enumeration/discovery, SQL injection in tool arguments, LDAP injection, template injection, environment variable extraction, file operation abuse, and serialization attacks. This rule focuses on parameter-level attacks rather than tool name matching, since tool names are easily changed but injection patterns in arguments are structurally consistent across attack variants.
|
|
@@ -8,7 +8,7 @@ author: ATR Community
|
|
|
8
8
|
date: 2026/03/08
|
|
9
9
|
schema_version: '0.1'
|
|
10
10
|
detection_tier: pattern
|
|
11
|
-
maturity:
|
|
11
|
+
maturity: experimental
|
|
12
12
|
severity: high
|
|
13
13
|
references:
|
|
14
14
|
owasp_llm:
|
|
@@ -64,12 +64,12 @@ detection:
|
|
|
64
64
|
conditions:
|
|
65
65
|
- field: tool_args
|
|
66
66
|
operator: regex
|
|
67
|
-
value: (\.\.[\/\\]){
|
|
68
|
-
description: Path traversal
|
|
67
|
+
value: (\.\.[\/\\]){4,}|\.\.[\/\\][^\s"'`]{0,60}[\/\\](etc|proc|root|boot|passwd|shadow|hosts)[\/\\]?|~[\/\\]\.\.[\/\\]
|
|
68
|
+
description: Path traversal that either goes 4+ levels up or lands on a sensitive root (v3 tightened, strict subset of v2). v2's bare `(\.\./){2,}` fired on every markdown relative link (`](../../CONNECTORS.md)`) — 147 of its 155 benign hits — and its target list included `var|home|tmp|dev|usr|opt|sys`, which are ordinary directory names in a RELATIVE source path (`../figma-use/references/var`). Both were removed; the traversal-to-sensitive-root form and `~/../` are kept.
|
|
69
69
|
- field: tool_args
|
|
70
70
|
operator: regex
|
|
71
|
-
value: (;|&&|\|\|)\s*(?:sudo\s+)?(?:curl\s+(?:-\S+\s+)*['"]?https?://(?!localhost|127\.0\.0\.1|0\.0\.0\.0)|wget\s+(?:-\S+\s+)*['"]?https?://(?!localhost|127\.)|\bnc\b\s+-?[a-z0-9]|ncat\s+\S|\b(?:ba)?sh\s+-[ic]|python[0-9]?\s+-c|perl\s+-e|ruby\s+-e|node\s+-e|\beval\s|rm\s+-[rf]|cat\s+/etc/(?:passwd|shadow|sudoers)|whoami|\bid\b\s*(?:;|&|\||$)|uname\s+-a|base64\s+(?:-d|--decode)|ifconfig|>&\s*/dev/tcp)|[$`]\([^)`]{0,80}(
|
|
72
|
-
description: Shell metacharacter chained to a genuinely dangerous command — external network fetch, pipe-to-shell / interpreter one-liner, recon (whoami/
|
|
71
|
+
value: (;|&&|\|\|)\s*(?:sudo\s+)?(?:curl\s+(?:-\S+\s+)*['"]?https?://(?!localhost|127\.0\.0\.1|0\.0\.0\.0)|wget\s+(?:-\S+\s+)*['"]?https?://(?!localhost|127\.)|\bnc\b\s+-?[a-z0-9]|ncat\s+\S|\b(?:ba)?sh\s+-[ic]|python[0-9]?\s+-c|perl\s+-e|ruby\s+-e|node\s+-e|\beval\s+["'$`(]|rm\s+-[rf]{1,2}\s+(?:-\S+\s+)*(?:/\s|/\*|~|\$HOME|\*|/(?:etc|var|usr|home|root|bin|Users)\b)|cat\s+/etc/(?:passwd|shadow|sudoers)|whoami|\bid\b\s*(?:;|&|\||$)|uname\s+-a|base64\s+(?:-d|--decode)|ifconfig|>&\s*/dev/tcp)|[$`]\([^)`]{0,80}(?:\bnc\b|\b(?:ba)?sh\s+-[ic]|whoami|base64\s+-d|/etc/(?:passwd|shadow))
|
|
72
|
+
description: 'Shell metacharacter chained to a genuinely dangerous command — external network fetch, pipe-to-shell / interpreter one-liner, recon (whoami/uname -a), or sensitive-file read. v2 tightened the `;|&&|||` branch (legit build chains like "npm i && bash setup.sh" no longer fire). v3 is a further strict subset: (a) the command-substitution branch drops `\bid\b` (matched the identifier `Id` in `$(aws ... --query ''Accounts[*].Id'')` and `${trade.id}`), `\bbash\b` (matched `$(gh completion -s bash)`), `\bsh\s+-` (matched the filename in `$(scripts/filter-sessions.sh -x)`) and `curl|wget` (15 hits — `MODEL_ID=$(curl -fsS "$BASE_URL/v1/models")` is the ordinary capture-a-response idiom and carries no execution); (b) `rm\s+-[rf]` now needs a root/home/wildcard target, because `&& rm -rf ./skills/my-skill` is normal cleanup; (c) `\beval\s` now needs an actual argument, because it matched the prose "**uv**; eval deps:".'
|
|
73
73
|
- field: tool_name
|
|
74
74
|
operator: regex
|
|
75
75
|
value: (?i)^(admin_|root_|sudo_|system_|internal_|debug_|maintenance_|privileged_|superuser_|__)
|
|
@@ -80,36 +80,36 @@ detection:
|
|
|
80
80
|
description: Tool enumeration attempts to discover available attack surface
|
|
81
81
|
- field: tool_args
|
|
82
82
|
operator: regex
|
|
83
|
-
value: (?i)('\s*(OR|AND
|
|
84
|
-
description: SQL injection
|
|
83
|
+
value: (?i)(['"]\s*(OR|AND)\s+['"]?\d+\s*['"]?\s*(=|<|>|--|\)|;|\bLIKE\b)|['"]\s*(UNION\s+(ALL\s+)?SELECT|INSERT\s+INTO|DELETE\s+FROM|UPDATE\s+\w{1,64}\s+SET|DROP\s+(TABLE|DATABASE)|ALTER\s+TABLE)\b[^\n]{0,160}(--|\/\*)|\bUNION\s+(ALL\s+)?SELECT\b|\bxp_cmdshell\b|;\s*(DROP\s+(TABLE|DATABASE)|DELETE\s+FROM|INSERT\s+INTO|UPDATE\s+\w{1,64}\s+SET|ALTER\s+TABLE|EXEC(UTE)?\s+(xp_|sp_))\b)
|
|
84
|
+
description: 'SQL injection in a tool parameter: quote-break tautology, quote-break followed by a DML/DDL statement, UNION SELECT, xp_cmdshell, or a stacked statement after `;`. v3 is a strict subset of v2. v2 matched BARE SQL keywords, which is not injection — `EXEC(UTE)?\s+` matched the English word "execute " (≈864 of its 1,139 benign hits, e.g. "you execute them automatically"), and `UPDATE\s+.*\s+SET` had an unbounded `.*` that spans a whole JSON-encoded document (src/hook-handler.ts encodes a newline as the two characters \ and n, so `.` never stops). Bare INSERT INTO / DELETE FROM / ALTER TABLE / DROP TABLE now require a quote-break AND a SQL comment terminator, or a stacked `;` in front. The tautology branch additionally requires a comparison or terminator after the digit: v2''s `["'']\s*(OR|AND)\s+["'']?\d` matched ordinary English — `"Cinematic braam, horror" or "8-bit retro jump sound"`, `period: "30d" or "7d"`, `choose 0.3" or 0.5" gaps`.'
|
|
85
85
|
- field: tool_args
|
|
86
86
|
operator: regex
|
|
87
|
-
value: (?i)(\$\{?\w*(
|
|
88
|
-
description:
|
|
87
|
+
value: (?i)(\$\{?\w*(SECRET|CREDENTIAL|ACCESS_KEY|PRIVATE|PASSWORD|PASSWD)\w*\}?|(process\.env\.|os\.environ\s*[\[\.]\s*\\?['"]?|System\.getenv\s*\(\s*\\?['"]?|getenv\s*\(\s*\\?['"]?|ENV\[\s*\\?['"]?)\w*(SECRET|CREDENTIAL|ACCESS_KEY|PRIVATE|PASSWORD|PASSWD)\w*|(dict|list|str|json\.dumps|pprint|print)\s*\(\s*os\.environ|os\.environ\.(items|copy|keys|values)\s*\()
|
|
88
|
+
description: 'Reference to a secret-bearing environment variable, or a wholesale dump of the environment. v3 is a strict subset of v2. v2 fired on the language construct alone — `process.env.` (167 benign hits), `os.environ` (51), `System.getenv` (12), `getenv(` (7) — and on the generic words KEY|TOKEN|PASS|PWD|AUTH, which match `$HF_TOKEN`, `${token}`, `$ai_input_tokens`, `${BRAVE_SEARCH_API_KEY}`: every integration doc on earth reads an API key from the environment, so those forms carry no attack signal. The env-reading constructs now have to name a secret-bearing variable, and a whole-environment dump (dict(os.environ), os.environ.items()) fires on its own. RESIDUAL BY DESIGN: bare `${OPENAI_API_KEY}` / `$GITHUB_TOKEN` references no longer fire here — see the dedicated exfiltration rules ATR-2026-00115, ATR-2026-01892, ATR-2026-02262, which require an egress sink.'
|
|
89
89
|
- field: tool_args
|
|
90
90
|
operator: regex
|
|
91
|
-
value: (?i)([\/\\](etc[\/\\](passwd|shadow|sudoers|ssh[\/\\]|ssl[\/\\])|proc[\/\\](self[\/\\]|\d+[\/\\])(environ|cmdline|maps|fd)|root[\/\\]\.(bash_history|ssh)|\.
|
|
92
|
-
description: Access to known sensitive files (credentials, config, keys)
|
|
91
|
+
value: (?i)([\/\\](etc[\/\\](passwd|shadow|sudoers|ssh[\/\\]|ssl[\/\\])|proc[\/\\](self[\/\\]|\d+[\/\\])(environ|cmdline|maps|fd)|root[\/\\]\.(bash_history|ssh)|\.git[\/\\]config|\.aws[\/\\]credentials|\.ssh[\/\\](id_rsa|authorized_keys)|wp-config\.php|\.htpasswd|\.netrc|\.pgpass)|(\.\.[\/\\]){2,}[^\s"'`]{0,40}\.env\b)
|
|
92
|
+
description: 'Access to known sensitive files (credentials, config, keys). v3 is a strict subset of v2: the bare `[\/\\]\.env` alternative (42 of v2''s 65 benign hits) now requires a 2+ level traversal in front, because `./.env`, `assets/.env.example`, `~/.env` and `docker compose --env-file ../.env` are how every project documents and wires its own configuration.'
|
|
93
93
|
- field: tool_args
|
|
94
94
|
operator: regex
|
|
95
|
-
value: (\{\{
|
|
96
|
-
description: Server-side template injection (Jinja2, Java EL, JSP)
|
|
95
|
+
value: (\{\{[^{}]{0,120}((config|self|request)\s*[\.\[]\s*\\?['"]?_|__class__|__builtins__|__import__|lipsum|cycler|joiner|namespace)[^{}]{0,120}\}\}|\$\{[^{}]{0,100}(Runtime|ProcessBuilder|getClass|forName)[^{}]{0,100}\}|<%[^%]{0,120}(Runtime|exec|system|eval)[^%]{0,120}%>)
|
|
96
|
+
description: 'Server-side template injection (Jinja2, Java EL, JSP). v3 is a strict subset of v2: v2''s unbounded lazy `.*?` gaps let a match start at a JavaScript template literal (`${config.name}`) and run forward across a whole JSON-encoded document until it found the literal word "exec" and any later closing brace — 349 of its 372 benign hits came from the `pre-tool-json-arg` shape for exactly that reason. The gaps are now brace-bounded and length-bounded, and `exec` is dropped from the `${...}` branch (Java EL RCE still matches on Runtime / ProcessBuilder / getClass / forName). `config` / `self` / `request` also have to be followed by an attribute access into a dunder — bare `{{ github.event.pull_request.title }}`, `{{config_file}}`, `{{ config(materialized=''view'') }}` (dbt) and `{{.Config.User}}` (Docker Go template) are template placeholders, not Jinja2 sandbox escapes.'
|
|
97
97
|
- field: tool_args
|
|
98
98
|
operator: regex
|
|
99
|
-
value: (?i)(O:\d+:\s*"|a:\d+:\s*\{|rO0ABX|aced0005|\{\s*"__type"\s
|
|
100
|
-
description: Serialized object injection (PHP, Java, Python pickle, YAML, .NET)
|
|
99
|
+
value: (?i)(O:\d+:\s*"|a:\d+:\s*\{|rO0ABX|aced0005|\{\s*"__type"\s*:|yaml\.unsafe_load|pickle\.loads|unserialize\s*\(|Marshal\.load|ObjectInputStream)
|
|
100
|
+
description: 'Serialized object injection (PHP, Java, Python pickle, YAML, .NET). v3 is a strict subset of v2: `{"$type":` is dropped — the W3C Design Tokens format uses `"$type"` as an ordinary schema key, so it is not evidence of .NET TypeNameHandling deserialization on its own.'
|
|
101
101
|
- field: tool_args
|
|
102
102
|
operator: regex
|
|
103
|
-
value: (?i)(\*\)\(|\
|
|
104
|
-
description: LDAP filter injection patterns
|
|
103
|
+
value: (?i)(\*\)\(|\(\|\(|\(&\()[^()]{0,60}(objectClass|uid|cn|sn|userPassword|memberOf)\s*[=~<>]
|
|
104
|
+
description: LDAP filter injection patterns. v3 is a strict subset of v2. v2's anchors included a bare `\)\(`, `\|\s*\(` and `&\s*\(` — `require('stripe')('sk_...')`, `sequenceS(validation)({...})` and JSX `&& (` all match — and the following `.*?` was unbounded, so it ran across a whole JSON-encoded document to find any later `mail=` / `uid=`. The gap is now paren-bounded and length-bounded, only the three genuinely LDAP-shaped anchors remain, and the English word `mail` is dropped from the attribute list.
|
|
105
105
|
- field: tool_args
|
|
106
106
|
operator: regex
|
|
107
|
-
value: (?i)(@|%40)(localhost|127\.0\.0\.1|0\.0\.0\.0|internal|intranet|corp|private)
|
|
108
|
-
description: URL manipulation to access internal resources via @ notation or exotic protocols
|
|
107
|
+
value: (?i)(https?|ftp|gopher|dict|ldap|tftp)://[^\s/"'<>]{0,120}(@|%40)(localhost|127\.0\.0\.1|0\.0\.0\.0|internal|intranet|corp|private)|url\s*=\s*['"]?(file|gopher|dict|ftp|ldap|tftp)://
|
|
108
|
+
description: 'URL manipulation to access internal resources via @ userinfo notation or exotic protocols. v3 is a strict subset of v2: the `@host` form now has to sit inside an actual URL. v2 matched a bare `@localhost` / `@corp` / `@private` anywhere, i.e. the e-mail address `admin@localhost.com`, the npm scope `@private/auth-form` and the connection string `postgresql://user:pass@localhost:5432/db`.'
|
|
109
109
|
- field: tool_args
|
|
110
110
|
operator: regex
|
|
111
|
-
value: (?i)(
|
|
112
|
-
description: Wildcard
|
|
111
|
+
value: (?i)(\bfind\s+/\s+-name|\bfind\s+/\s+-exec|\bxargs\s+(-\S+\s+)*(rm|sh|bash|zsh|curl|wget|chmod|chown|dd|cat|base64|nc)\b|\*\s*(;|&&|\|\|)\s*(rm|curl|wget|sh|bash|nc|cat|python|perl|tar|zip|base64)\b)
|
|
112
|
+
description: Wildcard / glob combined with an actually dangerous command. v3 is a strict subset of v2. v2 fired on `\*\s*;` (44 benign hits — any `**;` in markdown or code), on a bare `\bxargs\s+` (15 hits — `find . -name "*.py" | xargs grep -l` is ordinary shell), and on `glob(...**` (ordinary globbing in application code). The chain now has to end in a destructive/exfiltrating command.
|
|
113
113
|
- field: tool_args
|
|
114
114
|
operator: regex
|
|
115
115
|
value: (?i)(admin\s*=\s*(true|1|yes)|role\s*=\s*(admin|root|superuser|system)|is_admin\s*=\s*(true|1)|privilege\s*=\s*(elevated|admin|root|full)|permission\s*=\s*(all|\*|admin|root)|access_level\s*=\s*(admin|root|full|unlimited))
|
|
@@ -126,7 +126,7 @@ response:
|
|
|
126
126
|
- alert
|
|
127
127
|
- snapshot
|
|
128
128
|
- escalate
|
|
129
|
-
actions_rationale: 'response-action eligibility (docs/RESPONSE-ACTION-ELIGIBILITY.md):
|
|
129
|
+
actions_rationale: 'response-action eligibility (docs/RESPONSE-ACTION-ELIGIBILITY.md): block_tool(interrupt) was removed when rule_version 2 measured 1672/5352 = 31.24% benign FP, over the 2% automatic-demotion line. rule_version 3 measures 150/14157 = 1.06% (partial measurement: the tool_name conditions are not measurable on a text corpus) and is maturity experimental; its detection fingerprint is new, so until data/benign-fp-measurement.json is regenerated the gate treats it as unmeasured and the ceiling is observe. alert, snapshot and escalate are all observe-tier.'
|
|
130
130
|
auto_response_threshold: high
|
|
131
131
|
message_template: |
|
|
132
132
|
[ATR-2026-012] Unauthorized tool call detected. Rule: ATR-2026-012. Tool: {tool_name}. Pattern: {matched_pattern}. This rule no longer blocks the tool; it alerts and snapshots. The hook may still deny on severity — see docs/RESPONSE-ACTION-ELIGIBILITY.md.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
title: SSRF via Agent Tool Calls
|
|
2
2
|
id: ATR-2026-00013
|
|
3
|
-
rule_version:
|
|
3
|
+
rule_version: 4
|
|
4
4
|
status: experimental
|
|
5
5
|
description: |
|
|
6
|
-
Detects high-confidence Server-Side Request Forgery (SSRF) that targets cloud CREDENTIAL / metadata services or uses deliberate obfuscation — the cases where an agent has no legitimate reason to make the request. Detection covers: AWS/GCP/Azure/DigitalOcean/Oracle/Alibaba metadata and IMDS credential endpoints and token headers, OBFUSCATED loopback encodings (hex/octal), exotic URI schemes (file, gopher, dict, tftp, ldap) targeting internal addresses, DNS-rebinding service hostnames, IPv6
|
|
6
|
+
Detects high-confidence Server-Side Request Forgery (SSRF) that targets cloud CREDENTIAL / metadata services or uses deliberate obfuscation — the cases where an agent has no legitimate reason to make the request. Detection covers: AWS/GCP/Azure/DigitalOcean/Oracle/Alibaba metadata and IMDS credential endpoints and token headers, OBFUSCATED loopback encodings (hex/octal), exotic URI schemes (file, gopher, dict, tftp, ldap) targeting internal addresses, DNS-rebinding service hostnames, IPv4-mapped IPv6 forms of loopback and private IPv4, redirect-parameter SSRF to internal targets, and internal-redirect URL shorteners. NOTE (v2, precision): plain localhost / 127.0.0.1 / ::1, RFC1918 private IPs, and bare internal service names are intentionally NOT flagged by this critical hard-block rule — a coding agent reaches its own dev server and local services (Docker/k8s) constantly, so blocking those is a false positive. Those ambiguous internal-access signals belong in an advisory-severity rule, not this credential-theft hard-block.
|
|
7
7
|
author: ATR Community
|
|
8
8
|
date: 2026/03/08
|
|
9
9
|
schema_version: '0.1'
|
|
@@ -24,6 +24,10 @@ references:
|
|
|
24
24
|
cve:
|
|
25
25
|
- CVE-2019-5418
|
|
26
26
|
- CVE-2021-21311
|
|
27
|
+
- CVE-2026-55096
|
|
28
|
+
external:
|
|
29
|
+
- https://nvd.nist.gov/vuln/detail/CVE-2026-55096
|
|
30
|
+
- https://github.com/leshchenko1979/fast-mcp-telegram/security/advisories/GHSA-xr72-j7vj-vp7g
|
|
27
31
|
compliance:
|
|
28
32
|
nist_ai_rmf:
|
|
29
33
|
- subcategory: MS.2.7
|
|
@@ -95,27 +99,34 @@ detection:
|
|
|
95
99
|
description: Exotic URI schemes targeting internal addresses
|
|
96
100
|
- field: tool_args
|
|
97
101
|
operator: regex
|
|
98
|
-
value: (?i)(https?://)?[a-zA-Z0-9-]
|
|
102
|
+
value: (?i)(https?://)?[a-zA-Z0-9-]{1,63}\.(xip\.io|nip\.io|sslip\.io|localtest\.me|vcap\.me|lvh\.me|lacolhost\.com|127\.0\.0\.1\.[a-z]+\.\w+)(:\d+)?
|
|
99
103
|
description: DNS rebinding services that resolve to internal IPs
|
|
104
|
+
- field: tool_args
|
|
105
|
+
operator: regex
|
|
106
|
+
value: (?i)\b(?:https?|wss?|ftp):(?:\\?/){2}(?:[^\s/\\@"'<>]{1,64}@)?(?:localtest\.me|lvh\.me|vcap\.me|lacolhost\.com)\.?(?:[:/?#"'\s\\\],;)>|]|$)
|
|
107
|
+
description: 'APEX loopback-wildcard domains used as a URL host. localtest.me, lvh.me, vcap.me and lacolhost.com resolve to 127.0.0.1 at the apex as well as on every subdomain, so a hostname-string SSRF denylist that does not resolve DNS lets https://localtest.me/admin reach loopback services (CVE-2026-55096, fast-mcp-telegram send_message files[]). The pattern above only covers the subdomain form; this one requires a URL scheme directly before the apex host (plain "//" or JSON-escaped "\/\/", optional userinfo, optional trailing dot) and then a host terminator character (: / ? # quote whitespace backslash ] , ; ) > |) or end of input, so prose mentions and look-alike hosts such as localtest.mesh.example.com, lvh.media, vcap.me.uk or lvh.me-cdn.net do not match. The terminator is consumed rather than asserted with a lookahead so the pattern stays RE2-portable. Known limitations (not covered): protocol-relative //localtest.me/admin with no scheme; other apex loopback domains such as localho.st or fuf.me; attacker-owned domains whose A record points at an internal IP (needs DNS resolution, not possible at the pattern layer). Known false positives: a developer reaching their own dev server through the lvh.me alias (curl http://lvh.me:3000/...), and a free-text search query that quotes the URL https://localtest.me - the existing subdomain condition above already flags app.lvh.me:3000, so this is consistent with it.'
|
|
100
108
|
- field: tool_args
|
|
101
109
|
operator: regex
|
|
102
110
|
value: (?i)(redirect|redir|url|next|return|returnUrl|returnTo|continue|dest|destination|go|goto|target|link|out|view|ref|callback|forward)\s*=\s*(https?%3A%2F%2F|https?://)(localhost|127\.0\.0\.1|10\.|172\.(1[6-9]|2[0-9])|192\.168|169\.254|0\.0\.0|\[?::1\]?)
|
|
103
111
|
description: URL redirect parameters targeting internal addresses
|
|
104
112
|
- field: tool_args
|
|
105
113
|
operator: regex
|
|
106
|
-
value: (?i)(
|
|
107
|
-
description: IPv6 loopback, IPv4-mapped, and unique local addresses (
|
|
114
|
+
value: (?i)(?:^|[^0-9a-f:]|%5b)\[?(?:(?:0{1,4}:){1,4}:|::|(?:0{1,4}:){5})ffff:(?:127\.\d{1,3}\.\d{1,3}\.\d{1,3}|10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|192\.168\.)
|
|
115
|
+
description: 'IPv4-mapped IPv6 forms (::ffff:a.b.c.d, also written 0::ffff:, 0:0::ffff: or fully expanded 0:0:0:0:0:ffff:) of loopback (127/8) and RFC 1918 IPv4, an obfuscation that slips past filters written for dotted IPv4. The address must start the token (start of input, a character that is not a hex digit or colon, or a percent-encoded bracket %5B), so the suffix of a longer IPv6 address does not count. Known limits: a mapped address directly after a colon (unbracketed host:port:addr syntax such as curl --resolve x:80:::ffff:127.0.0.1) and a JSON-escaped bracket (\u005b) are not matched; the hex form ::ffff:7f00:1, IPv4-compatible ::127.0.0.1 and NAT64 64:ff9b:: forms are left to ATR-2026-02868; ::ffff:0.0.0.0 and ::ffff:100.64.0.0/10 are not covered. Plain ::1 and unique local addresses (fc00::/7) are not matched, for the same reason plain localhost, 127.0.0.1 and RFC 1918 are not (see the v2 note in the description): a coding agent reaches its own dev server on [::1] and private services on ULA all the time. v3 matched both with no token boundary, so the tail of a public 6to4 address (2002:cb00:7101::1), ::10, and Oracle APEX URLs (:::190) matched as loopback.'
|
|
108
116
|
- field: tool_args
|
|
109
117
|
operator: regex
|
|
110
118
|
value: (?i)(https?://)?(bit\.ly|tinyurl\.com|t\.co|goo\.gl|is\.gd|v\.gd|rb\.gy|cutt\.ly|short\.io|tiny\.cc)/[a-zA-Z0-9]+
|
|
111
119
|
description: URL shorteners that may redirect to internal services
|
|
112
120
|
condition: any
|
|
113
121
|
false_positives:
|
|
122
|
+
- Logs and diagnostics pasted into a tool argument that report IPv4 peers in IPv4-mapped form, as dual-stack sockets do on Linux (netstat / ss / lsof output, Node "connect ECONNREFUSED ::ffff:127.0.0.1:6379", Express req.ip ::ffff:192.168.1.10, nginx or MySQL messages with ::ffff:172.17.0.1). Loopback addresses of system resolvers such as ::ffff:127.0.0.53 match too, since 127/8 is covered.
|
|
114
123
|
- Agents with legitimate access to internal services explicitly allowlisted
|
|
115
124
|
- Development environments where localhost access is expected
|
|
116
125
|
- Private network tools designed for internal infrastructure management
|
|
117
126
|
- Cloud management tools that legitimately query metadata endpoints
|
|
118
127
|
- Service mesh tools that use .internal or .consul DNS names by design
|
|
128
|
+
- Developers who use the lvh.me / localtest.me / vcap.me / lacolhost.com loopback aliases for their own dev server (common in the Rails community, e.g. curl http://lvh.me:3000/users/sign_in) - both the apex and the subdomain forms are flagged, unlike plain localhost which v2 deliberately allows. Maintainers should decide whether these aliases belong in this hard-block rule or an advisory-severity rule.
|
|
129
|
+
- Free-text tool arguments (search queries, notes) that quote a full https://localtest.me style URL while discussing it rather than requesting it.
|
|
119
130
|
response:
|
|
120
131
|
actions:
|
|
121
132
|
- block_tool
|
|
@@ -21,7 +21,7 @@ references:
|
|
|
21
21
|
- AML.T0051.001 - Indirect
|
|
22
22
|
- AML.T0053 - AI Agent Tool Invocation
|
|
23
23
|
- 'AML.T0110.000 - AI Agent Tool Poisoning: Definition and Instructions'
|
|
24
|
-
- AML.
|
|
24
|
+
- 'AML.T0115.002 - Publish Poisoned AI Artifacts: AI Agent Tools'
|
|
25
25
|
safe_mcp:
|
|
26
26
|
- SAF-T1102
|
|
27
27
|
- SAF-T1001
|
|
@@ -65,11 +65,11 @@ detection:
|
|
|
65
65
|
conditions:
|
|
66
66
|
- field: tool_response
|
|
67
67
|
operator: regex
|
|
68
|
-
value: (?:i\s+am\s+(?:an?\s+)?(?:administrator|admin|sysadmin|root|superuser)\s+testing|when\s+I\s+prefix\s+(?:a\s+statement|(?:it\s+)?with)\s+["\x27]?ECHO[\s:]["\x27]?)
|
|
68
|
+
value: (?:i\s+am\s+(?:an?\s+)?(?:administrator|admin|sysadmin|root|superuser)\s+testing|when\s+I\s+prefix\s+(?:a\s+statement|(?:it\s+)?with)\s+["\x27]?ECHO[\s:]["\x27]?)(?:\s*,)?\s*(?:you\s+should\s+)?(?:respond|reply|echo|output|repeat|return)\s+(?:with\s+)?exactly\s+what\s+follows
|
|
69
69
|
description: 'Garak ECHO_TEMPLATE admin preamble: "administrator testing echo — when prefixed with ECHO: respond exactly"'
|
|
70
70
|
- field: tool_response
|
|
71
71
|
operator: regex
|
|
72
|
-
value: (?:^|\n)\
|
|
72
|
+
value: (?:^|\n)[^\S\n]*ECHO\s*:\s*(?:\{\{[^}]{1,200}\}\}|[`$]\([^)]{1,200}\)|\$\{[^}]{1,200}\}|<\?(?:php|=)[^?]{1,200}\?>|import\s+os|__import__|subprocess|eval\s*\(|exec\s*\()
|
|
73
73
|
description: 'ECHO: prefix followed by template expression, command substitution, or code execution payload'
|
|
74
74
|
- field: tool_response
|
|
75
75
|
operator: regex
|
|
@@ -80,15 +80,15 @@ detection:
|
|
|
80
80
|
description: SQL UNION SELECT injection for data exfiltration
|
|
81
81
|
- field: user_input
|
|
82
82
|
operator: regex
|
|
83
|
-
value: (?i)(and|or|if)\s
|
|
83
|
+
value: (?i)(and|or|if)\s*(?:\(\s*)?(substring|substr|mid|left|right|ascii|char|ord)\s*\(.{0,40}\)\s*=\s*['"]?[a-z0-9]|and\s*if\s*\(.{0,60}(sleep|waitfor\s+delay|benchmark)\s*\(
|
|
84
84
|
description: Blind boolean/time-based SQL injection via character extraction and delay functions
|
|
85
85
|
- field: user_input
|
|
86
86
|
operator: regex
|
|
87
|
-
value: (?i)'\s
|
|
87
|
+
value: (?i)'\s*(?:;\s*)?insert\s+into\s+[a-z_]+\s*(?:\([^)]{0,100}\)\s*)?values\s*\(
|
|
88
88
|
description: SQL INSERT INTO injection payload (column list optional)
|
|
89
89
|
- field: user_input
|
|
90
90
|
operator: regex
|
|
91
|
-
value: (?i)[0-9]
|
|
91
|
+
value: (?i)[0-9]\s*;\s*drop\s+table\s+[a-z_]+\s*(--|;|/\*)
|
|
92
92
|
description: SQL DROP TABLE destructive injection
|
|
93
93
|
- field: user_input
|
|
94
94
|
operator: regex
|
|
@@ -100,7 +100,7 @@ detection:
|
|
|
100
100
|
description: SQL server shutdown or command execution via stored procedure
|
|
101
101
|
- field: user_input
|
|
102
102
|
operator: regex
|
|
103
|
-
value: (?i)\(lambda\s
|
|
103
|
+
value: (?i)\(lambda\s[a-z_,\s]+:\s*\[+(?:[^\]\[][^\]]{0,199})?(s\.connect|socket\.connect|os\.system|subprocess|exec|eval).{0,100}\d{4,5}\)
|
|
104
104
|
description: Python lambda-based reverse shell or code execution payload
|
|
105
105
|
condition: any
|
|
106
106
|
false_positives:
|
package/rules/tool-poisoning/ATR-2026-00581-mcp-tool-rug-pull-post-approval-redefinition.yaml
CHANGED
|
@@ -20,7 +20,7 @@ references:
|
|
|
20
20
|
mitre_atlas:
|
|
21
21
|
- AML.T0053 - AI Agent Tool Invocation
|
|
22
22
|
- AML.T0051.001 - Indirect
|
|
23
|
-
- AML.
|
|
23
|
+
- 'AML.T0115.002 - Publish Poisoned AI Artifacts: AI Agent Tools'
|
|
24
24
|
vulnerablemcp_id:
|
|
25
25
|
- tool-poisoning-rce-rug-pull
|
|
26
26
|
external:
|
|
@@ -56,7 +56,7 @@ detection:
|
|
|
56
56
|
conditions:
|
|
57
57
|
- field: content
|
|
58
58
|
operator: regex
|
|
59
|
-
value: (?i)(?:list|show|give|tell\s+me|enumerate)\s+(?:me\s+)?(?:all\s+)?(?:the\s+)?(?:internal|hidden|admin(?:-only)?|privileged?|every)
|
|
59
|
+
value: (?i)(?:list|show|give|tell\s+me|enumerate)\s+(?:me\s+)?(?:all\s+)?(?:the\s+)?(?:(?:internal|hidden|admin(?:-only)?|privileged?|every)\s*)?(?:functions?\s+or\s+tools?|tools?\s+(?:you\s+can\s+call|or\s+functions?)|internal\s+(?:functions?|tools?|api\s+endpoints?))[^.]{0,80}(?:you\s+can\s+call|admin-only|including\s+(?:any\s+)?admin|with\s+their\s+parameters|access\s+to)
|
|
60
60
|
description: Request to enumerate the agent's own internal/admin tools or functions
|
|
61
61
|
- field: content
|
|
62
62
|
operator: regex
|
|
@@ -62,11 +62,11 @@ detection:
|
|
|
62
62
|
conditions:
|
|
63
63
|
- field: content
|
|
64
64
|
operator: regex
|
|
65
|
-
value: (?i)\b[a-zA-Z0-9.-]
|
|
65
|
+
value: (?i)\b[a-zA-Z0-9.-]{1,253}\.(?:rebind\.network|rbndr\.us|rebinder\.com|1u\.ms)\b
|
|
66
66
|
description: Known DNS rebinding service hostname (rebind.network, rbndr.us, etc.)
|
|
67
67
|
- field: content
|
|
68
68
|
operator: regex
|
|
69
|
-
value: (?i)
|
|
69
|
+
value: (?i)(?:1time|forever|ttl\d+|0ttl)[\w.-]{1,253}(?:127\.0\.0\.1|192\.168\.\d+\.\d+|10\.\d+\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[0-1])\.\d+\.\d+)
|
|
70
70
|
description: Time-based DNS rebinding hostname pattern combining public IP with loopback/private IP
|
|
71
71
|
- field: content
|
|
72
72
|
operator: regex
|
|
@@ -34,7 +34,7 @@ references:
|
|
|
34
34
|
- ASI02:2026
|
|
35
35
|
mitre_atlas:
|
|
36
36
|
- AML.T0053 - AI Agent Tool Invocation
|
|
37
|
-
- AML.
|
|
37
|
+
- 'AML.T0115.000 - Publish Poisoned AI Artifacts: Datasets'
|
|
38
38
|
- AML.T0051.001 - Indirect
|
|
39
39
|
- 'AML.T0110.000 - AI Agent Tool Poisoning: Definition and Instructions'
|
|
40
40
|
compliance:
|
|
@@ -19,7 +19,7 @@ references:
|
|
|
19
19
|
- ASI09:2026
|
|
20
20
|
mitre_atlas:
|
|
21
21
|
- AML.T0010 - AI Supply Chain Compromise
|
|
22
|
-
- AML.
|
|
22
|
+
- 'AML.T0115.002 - Publish Poisoned AI Artifacts: AI Agent Tools'
|
|
23
23
|
mitre_attack:
|
|
24
24
|
- T1195.002 - Compromise Software Supply Chain
|
|
25
25
|
- T1036 - Masquerading
|
package/rules/tool-poisoning/ATR-2026-02025-mcp-full-schema-poisoning-nondescription-field.yaml
CHANGED
|
@@ -20,7 +20,7 @@ references:
|
|
|
20
20
|
mitre_atlas:
|
|
21
21
|
- AML.T0051.001 - Indirect
|
|
22
22
|
- 'AML.T0110.000 - AI Agent Tool Poisoning: Definition and Instructions'
|
|
23
|
-
- AML.
|
|
23
|
+
- 'AML.T0115.002 - Publish Poisoned AI Artifacts: AI Agent Tools'
|
|
24
24
|
safe_mcp:
|
|
25
25
|
- SAF-T1001
|
|
26
26
|
research:
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
title: Untrusted Tool Result Dictates a Tool Call by Schema Parameter Name
|
|
2
|
+
id: ATR-2026-02817
|
|
3
|
+
rule_version: 1
|
|
4
|
+
status: experimental
|
|
5
|
+
description: |
|
|
6
|
+
Detects untrusted content inside a tool result that speaks the agent's OWN tool-schema vocabulary: it names the target function and binds each parameter by its literal schema name to a chosen value, then asks the assistant to construct exactly those arguments and issue the call. This is not an imperative wish ("please transfer 500 dollars") — which ATR already covers — it is a serialised call specification planted in a bill description, a review body or a message body, so the model's remaining work is transcription rather than intent formation. Source: AutoDojo variant generation over the AgentDojo banking and slack suites (agentdojo/variant_generation/variants/banking/.../injections.json and banking_rlhammer.json). Measured before authoring: 4 verbatim variants of this shape produced 1 hit across 785 rules, and that hit was ATR-2026-00504 firing incidentally on the words "full ... overview ... tools", not on the call-spec structure. TWO CONJUNCTS, BOTH REQUIRED. (1) An AGENT-DIRECTED request to emit the call. A bare "call the send_money function with the parameter recipient set to ..." is deliberately NOT matched, because that is exactly how API reference documentation, OpenAPI descriptions, SDK guides, changelog migration notes and bug-report repro steps read; six such benign twins were authored and all six match the parameter-binding condition alone. (2) TWO OR MORE named parameter bindings within 300 characters, which is what makes the text a call spec rather than a sentence that happens to mention an argument. CHANNEL IS PART OF THE DETECTION, NOT AN ACCIDENT. agent_source.type is mcp_exchange, so EVENT_TYPE_TO_SOURCE routes only tool_response and tool_call events here and llm_input is never evaluated. That is load-bearing: a user who types "I would like you to call the geocode function with the parameter address set to ..." is making an ordinary direct request, and it matches both conditions. It is benign because of WHO said it, and the event type is the only place that fact exists. Do not widen this rule to llm_io.
|
|
7
|
+
author: ATR Project
|
|
8
|
+
date: 2026/08/24
|
|
9
|
+
schema_version: '1.0'
|
|
10
|
+
detection_tier: pattern
|
|
11
|
+
maturity: test
|
|
12
|
+
severity: high
|
|
13
|
+
references:
|
|
14
|
+
owasp_llm:
|
|
15
|
+
- LLM01:2025
|
|
16
|
+
owasp_agentic:
|
|
17
|
+
- ASI01:2026
|
|
18
|
+
- ASI02:2026
|
|
19
|
+
mitre_atlas:
|
|
20
|
+
- AML.T0051 - LLM Prompt Injection
|
|
21
|
+
- AML.T0053 - LLM Plugin Compromise
|
|
22
|
+
papers:
|
|
23
|
+
- 'AutoDojo: automated variant generation for AgentDojo prompt-injection suites — repo: https://github.com/xhOwenMa/AutoDojo, agentdojo/variant_generation/variants/banking/anthropic/claude-haiku-4.5/no_defense/injections.json and banking_rlhammer.json'
|
|
24
|
+
- 'Debenedetti et al., AgentDojo: A Dynamic Environment to Evaluate Prompt Injection Attacks and Defenses for LLM Agents (arXiv:2406.13352)'
|
|
25
|
+
compliance:
|
|
26
|
+
nist_ai_rmf:
|
|
27
|
+
- subcategory: MS.2.7
|
|
28
|
+
context: A call specification planted in untrusted tool output is a prompt-injection payload aimed at the agent's action surface; MS.2.7 requires that resilience to such injection be evaluated and documented, and this rule supplies the runtime evidence.
|
|
29
|
+
strength: primary
|
|
30
|
+
- subcategory: MG.2.3
|
|
31
|
+
context: MG.2.3 requires mechanisms to disengage or override an AI system that behaves outside intended use; detecting dictated tool arguments is the trigger condition for that override before the call is issued.
|
|
32
|
+
strength: secondary
|
|
33
|
+
- subcategory: MS.1.1
|
|
34
|
+
context: MS.1.1 requires that risks be measured against intended use; a tool call whose arguments originate in retrieved third-party data is outside the agent's intended authority chain, which this rule makes measurable.
|
|
35
|
+
strength: secondary
|
|
36
|
+
iso_42001:
|
|
37
|
+
- clause: '8.1'
|
|
38
|
+
context: ISO/IEC 42001 Clause 8.1 (operational planning and control, including control of externally provided processes) is operationalised by detecting third-party content that attempts to originate the agent's tool calls.
|
|
39
|
+
strength: primary
|
|
40
|
+
- clause: 6.1.2
|
|
41
|
+
context: ISO/IEC 42001 Clause 6.1.2 (AI risk assessment) identifies untrusted-input-driven action as a treated risk; this rule is the corresponding detective control.
|
|
42
|
+
strength: secondary
|
|
43
|
+
eu_ai_act:
|
|
44
|
+
- article: '15'
|
|
45
|
+
context: Article 15 (accuracy, robustness and cybersecurity) requires high-risk AI systems to resist third-party attempts to alter their use or outputs; a dictated tool call is a direct alteration of use, detected here before execution.
|
|
46
|
+
strength: primary
|
|
47
|
+
- article: '14'
|
|
48
|
+
context: Article 14 (human oversight) requires that a natural person can intervene before consequential automated action; flagging an externally dictated call spec is what creates that intervention point.
|
|
49
|
+
strength: secondary
|
|
50
|
+
- article: '9'
|
|
51
|
+
context: Article 9 (risk management system) requires identified risks to be addressed by appropriate measures; this rule is a runtime risk-treatment control for indirect prompt injection targeting tool invocation.
|
|
52
|
+
strength: secondary
|
|
53
|
+
tags:
|
|
54
|
+
category: tool-poisoning
|
|
55
|
+
subcategory: dictated-tool-arguments
|
|
56
|
+
scan_target: content
|
|
57
|
+
confidence: medium
|
|
58
|
+
agent_source:
|
|
59
|
+
type: mcp_exchange
|
|
60
|
+
framework:
|
|
61
|
+
- any
|
|
62
|
+
provider:
|
|
63
|
+
- any
|
|
64
|
+
detection:
|
|
65
|
+
conditions:
|
|
66
|
+
- field: content
|
|
67
|
+
operator: regex
|
|
68
|
+
value: (?:\bI\s+(?:would\s+like|want|need|am\s+asking)\s+(?:you|the\s+(?:assistant|agent|ai|model|bot))\s+to\s+(?:call|invoke|execute|run|trigger|use)\b|\b(?:can|could|would|will)\s+you\s+(?:please\s+)?(?:construct|build|create|assemble|prepare|produce|form)\b[^.\n]{0,80}?\b(?:argument|parameter)s?\b[^.\n]{0,80}?\b(?:call|invoke|execute|trigger)\b|\b(?:have|let|ask)\s+the\s+(?:assistant|agent|ai|model|bot)\s+(?:to\s+)?(?:call|invoke|execute|trigger)\b|\bthe\s+(?:assistant|agent|ai|model|bot)\s+(?:should|must|needs\s+to|is\s+to)\s+(?:call|invoke|execute|trigger)\s+the\s+[A-Za-z_][A-Za-z0-9_]{1,40}\s+(?:function|tool|api|method|endpoint|command)\b)
|
|
69
|
+
description: The retrieved content asks an assistant/agent to construct and issue the call — the conjunct that separates a planted call spec from API reference documentation
|
|
70
|
+
- field: content
|
|
71
|
+
operator: regex
|
|
72
|
+
value: (?:\bthe\s+[A-Za-z_][A-Za-z0-9_]{1,40}\s+(?:parameter|argument|field)\b|\b(?:parameter|argument|field)\s+[A-Za-z_][A-Za-z0-9_]{1,40}\s+(?:\bset\s+to|=|\bas|\bto)\b|\bpassing\s+[^.\n]{0,60}?\bto\s+the\s+[A-Za-z_][A-Za-z0-9_]{1,40}\s+(?:parameter|argument|field)\b)[\s\S]{0,300}?(?:\bthe\s+[A-Za-z_][A-Za-z0-9_]{1,40}\s+(?:parameter|argument|field)\b|\b(?:parameter|argument|field)\s+[A-Za-z_][A-Za-z0-9_]{1,40}\s+(?:\bset\s+to|=|\bas|\bto)\b|\bpassing\s+[^.\n]{0,60}?\bto\s+the\s+[A-Za-z_][A-Za-z0-9_]{1,40}\s+(?:parameter|argument|field)\b)
|
|
73
|
+
description: Two or more named parameter bindings within 300 chars, i.e. the text is a serialised call specification rather than prose mentioning an argument
|
|
74
|
+
condition: all
|
|
75
|
+
false_positives:
|
|
76
|
+
- A support ticket, bug report or vendor email in which a human legitimately asks the agent to run a specific tool with specific arguments. The text is indistinguishable; what differs is whether the requester is authorised, which is not a property of the content.
|
|
77
|
+
- Security research or vendor writing that quotes an AutoDojo banking/slack injection variant verbatim.
|
|
78
|
+
- Agent-framework tutorials retrieved as documentation that phrase an example as a request to the assistant.
|
|
79
|
+
response:
|
|
80
|
+
actions:
|
|
81
|
+
- alert
|
|
82
|
+
message_template: |
|
|
83
|
+
[ATR-2026-02817] A tool result contains a fully specified tool call — target function plus parameter-by-parameter argument bindings — addressed to the assistant. Arguments originating in retrieved third-party data must not be executed without human confirmation.
|