Skip to content
ATR-2026-02350high工具下毒experimental

MCP JSON-RPC Message Carries Case-Duplicate name/arguments Keys to Smuggle an Unauthorized Tool Call

Detects a JSON-RPC 2.0 `tools/call` message that carries two differently -cased copies of the same logical key (`name`/`Name`, `arguments`/ `Arguments`) inside the same `params` object. The JSON-RPC spec used by MCP requires member-name matching to be case-sensitive, but a case-insensitive intermediary or SDK (e.g. Go's common JSON-decoding behaviour into case-insensitively-matched struct fields) can pick the differently-cased field over the one a security gateway validated, letting an attacker smuggle an unauthorized tool name or argument set past an authorization layer that only inspected the canonical-case key. Mined from GHSA-4gph-2hhr-5mwg (Envoy AI Gateway protocol-parser differential: an incoming message declaring both `"name":"backend__greet"` and `"Name":"backend__secretTool"` in the same `params` object is security-checked against `name` but a downstream MCP library resolves `Name`, so the gateway's multi-layered access control is bypassed and the unauthorized `backend__secretTool` call reaches the backend). Generalized beyond Envoy to any MCP gateway/proxy/client exhibiting the same case-insensitive parser differential, since the exploitable signal is the wire message shape itself (duplicate case-variant keys), not a vendor-specific string.

嚴重度
high
類別
工具下毒
掃描目標
both
作者
ATR Community (CVE sweep)

建議回應

block toolalert

參考資料

OWASP Agentic
ASI02:2026 - Tool Misuse and Exploitation
OWASP LLM
LLM06:2025 - Excessive Agency
MITRE ATLAS
AML.T0053 - LLM Plugin Compromise

偵測條件

組合方式: any
  1. 01
    The tool-call params object's own `name` field (the tool identifier -- anchored to immediately follow the params opening brace) is duplicated with a capitalized `Name` sibling. Anchoring to `params: { name` (rather than a loose jsonrpc...name search) deliberately excludes an unrelated name/Name pair nested one level deeper inside an arguments payload (e.g. a CRM contact record with both a slug `name` and a display `Name`), which is a real-world false positive this rule must not fire on.
    欄位: content運算子: regex
  2. 02
    Reverse key order variant -- capitalized `Name` declared as the first key in params before lowercase `name`, since JSON object key order is not guaranteed. Same params-anchor rationale as the forward-order condition.
    欄位: content運算子: regex
  3. 03
    The tool-call params object carries both a lowercase `arguments` object and a capitalized `Arguments` object as siblings -- anchored to the params opening brace (allowing the `name` field to sit before `arguments`, per the canonical PoC shape) so this does not fire on an unrelated arguments/Arguments pair nested deeper inside unrelated data. The flat-object bound on the first arguments value (no nested braces) covers the canonical exploit shape; deeply nested argument objects are a known limitation, see evasion_tests.
    欄位: content運算子: regex

攻擊範例(規則會觸發)

  1. {"id":1,"jsonrpc":"2.0","method":"tools/call","params":{"name":"backend__greet","Name":"backend__secretTool","arguments":{"input":"hi"},"Arguments":{"admin":true}}}
  2. {"jsonrpc":"2.0","method":"tools/call","id":7,"params":{"name":"list_files","Name":"delete_all_files"}}
  3. {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"Name":"admin_reset","name":"get_status"}}
  4. {"jsonrpc":"2.0","method":"tools/call","params":{"name":"transfer_funds","arguments":{"amount":1},"Arguments":{"amount":999999,"account":"attacker"}}}

以上為真實攻擊 payload 的脫敏版本,與規則一同版本化,作為 regression test——確保未來的修訂不會悄悄漏掉它們。

正常樣本(規則不會觸發)

  1. Ordinary single-case tool call -- no duplicate-cased keys
    {"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"backend__greet","arguments":{"name":"World"}}}
  2. Spec-text prose describing case-sensitivity requirement, no actual duplicate-keyed message
    The JSON-RPC 2.0 spec requires member names to be case-sensitive: all member names exchanged between Client and Server should be considered case-sensitive.
  3. Ordinary non-MCP data record with a slug-style `name` and display-style `Name` field -- no jsonrpc/tools-call envelope context
    {"name": "john.doe", "Name": "John Doe", "role": "contact"}
  4. Fields containing 'Name' as a substring (firstName/lastName) are not the literal duplicated key 'Name' -- regex requires the exact quoted key
    {"jsonrpc":"2.0","method":"tools/call","params":{"name":"greet","arguments":{"firstName":"Ann","lastName":"Lee"}}}
  5. ADVERSARIAL SELF-REVIEW FINDING (fixed): a legitimate create_contact tool call whose arguments payload has both a slug-style name and a display-style Name for the CONTACT record, not the tool identity itself. An earlier, un-anchored version of this rule's regex matched this (false positive) because its bounded gap could reach across object boundaries into the nested arguments; the params-anchored regex (requiring `name` to be the first key immediately after `params: {`) no longer matches nested duplicates and only fires on the actual tool-identity field.
    {"jsonrpc":"2.0","method":"tools/call","params":{"name":"create_contact","arguments":{"name":"jdoe","Name":"John Doe","email":"[email protected]"}}}

已知誤報情境

  • Security research documentation, RFC/spec excerpts (e.g. the JSON-RPC case-sensitivity spec text itself), or vulnerability write-ups that quote the example PoC JSON verbatim for explanatory purposes rather than an actual live tool-call message
  • A tool-call arguments payload containing both a slug-style `name` and a display-style `Name` field for a data record being created/updated (e.g. a CRM contact, a form submission) -- this is nested one level inside the params object's `arguments` value, not a duplicate of the params-level tool-identity `name` field itself, and the params-anchored regex does not match it
  • ATR test fixtures containing this pattern as a labeled attack sample
  • A non-MCP JSON payload that happens to define both a `name` and unrelated `Name` field for legitimate display-vs-slug purposes -- mitigated by requiring the jsonrpc/tools-call envelope context, which ordinary data records do not carry

已記錄的規避手法

  1. 手法: nested braces in arguments value
    {"jsonrpc":"2.0","method":"tools/call","params":{"name":"greet","arguments":{"nested":{"a":1}},"Arguments":{"nested":{"b":2}}}}
    The arguments/Arguments condition bounds the first arguments value to a flat object with no nested braces ([^{}]{0,120}); a nested object breaks that bound and the pattern does not match. The name/Name conditions are unaffected by this limitation and still catch the tool-identity-swap variant of the same underlying bug.

這些是公開記錄的繞過手法。一個標準的可信度,取決於它願不願意公開自己最差的數字——所以已知限制寫在規則裡,而不是藏起來。

完整 YAML 定義

在 GitHub 編輯 →
title: "MCP JSON-RPC Message Carries Case-Duplicate name/arguments Keys to Smuggle an Unauthorized Tool Call"
id: ATR-2026-02350
rule_version: 1
status: experimental
description: >
  Detects a JSON-RPC 2.0 `tools/call` message that carries two differently
  -cased copies of the same logical key (`name`/`Name`, `arguments`/
  `Arguments`) inside the same `params` object. The JSON-RPC spec used by
  MCP requires member-name matching to be case-sensitive, but a
  case-insensitive intermediary or SDK (e.g. Go's common JSON-decoding
  behaviour into case-insensitively-matched struct fields) can pick the
  differently-cased field over the one a security gateway validated,
  letting an attacker smuggle an unauthorized tool name or argument set
  past an authorization layer that only inspected the canonical-case key.
  Mined from GHSA-4gph-2hhr-5mwg (Envoy AI Gateway protocol-parser
  differential: an incoming message declaring both `"name":"backend__greet"`
  and `"Name":"backend__secretTool"` in the same `params` object is
  security-checked against `name` but a downstream MCP library resolves
  `Name`, so the gateway's multi-layered access control is bypassed and the
  unauthorized `backend__secretTool` call reaches the backend). Generalized
  beyond Envoy to any MCP gateway/proxy/client exhibiting the same
  case-insensitive parser differential, since the exploitable signal is the
  wire message shape itself (duplicate case-variant keys), not a
  vendor-specific string.
author: "ATR Community (CVE sweep)"
date: "2026/07/11"
schema_version: "0.1"
detection_tier: pattern
maturity: experimental
severity: high

references:
  cwe:
    - "CWE-178"
  owasp_llm:
    - "LLM06:2025 - Excessive Agency"
  owasp_agentic:
    - "ASI02:2026 - Tool Misuse and Exploitation"
  mitre_attack:
    - "T1036 - Masquerading"
  mitre_atlas:
    - "AML.T0053 - LLM Plugin Compromise"
  external:
    - "https://github.com/advisories/GHSA-4gph-2hhr-5mwg"
    - "https://github.com/envoyproxy/ai-gateway/security/advisories/GHSA-4gph-2hhr-5mwg"

metadata_provenance:
  cwe: human-reviewed
  owasp_llm: human-reviewed
  owasp_agentic: human-reviewed
  mitre_attack: human-reviewed
  mitre_atlas: human-reviewed

compliance:
  eu_ai_act:
    - article: "15"
      context: "Article 15 (accuracy, robustness and cybersecurity) requires resistance to unauthorised manipulation of system behaviour; this rule detects a JSON-RPC/MCP message carrying case-duplicate keys used to smuggle an unauthorized tool call past a security gateway that validated the wrong-cased field."
      strength: primary
    - article: "9"
      context: "Article 9 (risk management system) requires identified risks to be addressed by appropriate measures; this rule is a runtime risk-treatment control for the protocol-parser-differential tool-smuggling risk class."
      strength: secondary
  nist_ai_rmf:
    - subcategory: "MG.2.3"
      context: "Treating case-duplicate-key MCP message smuggling as an identified AI risk requires active runtime countermeasures; this detection rule is the primary risk treatment implementation."
      strength: primary
    - subcategory: "MP.5.1"
      context: "Identifying the protocol-parser-differential smuggling technique as an AI risk to be catalogued in the organizational risk register."
      strength: secondary
  iso_42001:
    - clause: "8.1"
      context: "ISO/IEC 42001 Clause 8.1 (operational planning and control) is operationalised by this rule's detection of case-duplicate-key tool-call smuggling attempts."
      strength: primary
    - clause: "8.3"
      context: "ISO/IEC 42001 Clause 8.3 (AI risk treatment) is implemented via this rule's runtime detection of the smuggling attempt."
      strength: secondary

tags:
  category: tool-poisoning
  subcategory: json-rpc-case-duplicate-key-smuggling
  scan_target: both
  confidence: medium

agent_source:
  type: mcp_exchange
  framework:
    - any
  provider:
    - any

detection:
  condition: any
  conditions:
    - field: content
      operator: regex
      value: '"params"\s*:\s*\{\s*"name"\s*:\s*"[^"]{1,100}"[^{}]{0,80}"Name"\s*:\s*"[^"]{1,100}"'
      description: "The tool-call params object's own `name` field (the tool identifier -- anchored to immediately follow the params opening brace) is duplicated with a capitalized `Name` sibling. Anchoring to `params: { name` (rather than a loose jsonrpc...name search) deliberately excludes an unrelated name/Name pair nested one level deeper inside an arguments payload (e.g. a CRM contact record with both a slug `name` and a display `Name`), which is a real-world false positive this rule must not fire on."
    - field: content
      operator: regex
      value: '"params"\s*:\s*\{\s*"Name"\s*:\s*"[^"]{1,100}"[^{}]{0,80}"name"\s*:\s*"[^"]{1,100}"'
      description: "Reverse key order variant -- capitalized `Name` declared as the first key in params before lowercase `name`, since JSON object key order is not guaranteed. Same params-anchor rationale as the forward-order condition."
    - field: content
      operator: regex
      value: '"params"\s*:\s*\{[^{}]{0,60}"arguments"\s*:\s*\{[^{}]{0,120}\}[^{}]{0,60}"Arguments"\s*:'
      description: "The tool-call params object carries both a lowercase `arguments` object and a capitalized `Arguments` object as siblings -- anchored to the params opening brace (allowing the `name` field to sit before `arguments`, per the canonical PoC shape) so this does not fire on an unrelated arguments/Arguments pair nested deeper inside unrelated data. The flat-object bound on the first arguments value (no nested braces) covers the canonical exploit shape; deeply nested argument objects are a known limitation, see evasion_tests."
  false_positives:
    - "Security research documentation, RFC/spec excerpts (e.g. the JSON-RPC case-sensitivity spec text itself), or vulnerability write-ups that quote the example PoC JSON verbatim for explanatory purposes rather than an actual live tool-call message"
    - "A tool-call arguments payload containing both a slug-style `name` and a display-style `Name` field for a data record being created/updated (e.g. a CRM contact, a form submission) -- this is nested one level inside the params object's `arguments` value, not a duplicate of the params-level tool-identity `name` field itself, and the params-anchored regex does not match it"
    - "ATR test fixtures containing this pattern as a labeled attack sample"
    - "A non-MCP JSON payload that happens to define both a `name` and unrelated `Name` field for legitimate display-vs-slug purposes -- mitigated by requiring the jsonrpc/tools-call envelope context, which ordinary data records do not carry"

response:
  actions:
    - block_tool
    - alert
  message_template: >
    [ATR-2026-02350] MCP/JSON-RPC message carries case-duplicate name or
    arguments keys in the same params object -- treat as an attempt to
    smuggle an unauthorized tool call past a security gateway that
    validated the wrong-cased field, not a normal tool invocation.

confidence: 72
wild_fp_rate: 0

test_cases:
  true_positives:
    - input: '{"id":1,"jsonrpc":"2.0","method":"tools/call","params":{"name":"backend__greet","Name":"backend__secretTool","arguments":{"input":"hi"},"Arguments":{"admin":true}}}'
      expected: triggered
      description: "GHSA-4gph-2hhr-5mwg canonical PoC -- both name/Name and arguments/Arguments duplicated"
    - input: '{"jsonrpc":"2.0","method":"tools/call","id":7,"params":{"name":"list_files","Name":"delete_all_files"}}'
      expected: triggered
      description: "Paraphrased variant duplicating only name/Name to swap a read-only tool for a destructive one"
    - input: '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"Name":"admin_reset","name":"get_status"}}'
      expected: triggered
      description: "Reverse key order -- Name declared before name"
    - input: '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"transfer_funds","arguments":{"amount":1},"Arguments":{"amount":999999,"account":"attacker"}}}'
      expected: triggered
      description: "arguments/Arguments duplication smuggling a different payload than the validated one"
  true_negatives:
    - input: '{"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"backend__greet","arguments":{"name":"World"}}}'
      expected: not_triggered
      description: "Ordinary single-case tool call -- no duplicate-cased keys"
    - input: 'The JSON-RPC 2.0 spec requires member names to be case-sensitive: all member names exchanged between Client and Server should be considered case-sensitive.'
      expected: not_triggered
      description: "Spec-text prose describing case-sensitivity requirement, no actual duplicate-keyed message"
    - input: '{"name": "john.doe", "Name": "John Doe", "role": "contact"}'
      expected: not_triggered
      description: "Ordinary non-MCP data record with a slug-style `name` and display-style `Name` field -- no jsonrpc/tools-call envelope context"
    - input: '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"greet","arguments":{"firstName":"Ann","lastName":"Lee"}}}'
      expected: not_triggered
      description: "Fields containing 'Name' as a substring (firstName/lastName) are not the literal duplicated key 'Name' -- regex requires the exact quoted key"
    - input: '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"create_contact","arguments":{"name":"jdoe","Name":"John Doe","email":"[email protected]"}}}'
      expected: not_triggered
      description: "ADVERSARIAL SELF-REVIEW FINDING (fixed): a legitimate create_contact tool call whose arguments payload has both a slug-style name and a display-style Name for the CONTACT record, not the tool identity itself. An earlier, un-anchored version of this rule's regex matched this (false positive) because its bounded gap could reach across object boundaries into the nested arguments; the params-anchored regex (requiring `name` to be the first key immediately after `params: {`) no longer matches nested duplicates and only fires on the actual tool-identity field."

evasion_tests:
  - input: '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"greet","arguments":{"nested":{"a":1}},"Arguments":{"nested":{"b":2}}}}'
    expected: not_triggered
    bypass_technique: nested_braces_in_arguments_value
    notes: "The arguments/Arguments condition bounds the first arguments value to a flat object with no nested braces ([^{}]{0,120}); a nested object breaks that bound and the pattern does not match. The name/Name conditions are unaffected by this limitation and still catch the tool-identity-swap variant of the same underlying bug."

修訂歷史

建立於
2026-07-11
最後修改
2026-07-14
在 GitHub 查看完整 commit 歷史 →