title: Option-Flag Smuggling in an LLM-Controlled Tool Parameter
id: ATR-2026-02528
rule_version: 1
status: experimental
description: |
  Detects a tool parameter that should carry DATA -- a search pattern, a filename, a hostname -- but instead carries an OPTION FLAG whose value names a program. The underlying CLI parses the value as an option rather than an operand and executes what it points at, so the agent never issues a shell command and no shell metacharacter ever appears.
  Derived from CVE-2026-48116 (AnythingLLM before 1.13.0): the filesystem-search-files agent skill passes its LLM-controlled `pattern` parameter to ripgrep as a positional argument with no `--` end-of-options separator. ripgrep treats any argument beginning with `-` as an option, so a pattern of `--pre=/bin/sh` turns the search tool into a script executor that runs the interpreter once per file it walks.
  WHY EXISTING COVERAGE MISSES THIS. Shell-metacharacter rules (ATR-2026-00111, ATR-2026-00066) key on `;`, `&&`, backticks, `$(`, pipes. A smuggled flag contains none of them: `--pre=/bin/sh` is a well-formed single token with no separator, no quoting, and no subshell. Measured against the engine before this rule was written, that exact parameter produced zero matches across the whole corpus. The invariant that does hold is positional: a value that is syntactically an option, sitting where the tool schema says an operand goes.
  GENERALISATION. The flag family is enumerated from the CLIs an agent actually drives, not from the one vendor: ripgrep --pre / --pre-glob, tar --to-command / --use-compress-program, wget --use-askpass, git --upload-pack / --receive-pack, sort --compress-program, mysql --pager, rsync --rsh, find -exec. Condition 2 generalises past the list entirely: ANY flag-shaped value whose payload is a path into a bin directory, appearing under a data-named JSON key.
  SSH IS OUT OF SCOPE ON PURPOSE. `-o ProxyCommand=` and its siblings belong to ATR-2026-02370, which owns the ssh/scp hostAlias surface and carries advisory prose about `-oProxyCommand=` as a declared true_negative. A condition here would have fired on that neighbour's benign set, which is a precision regression for it, so the ssh family was removed rather than duplicated.
  BOUNDARY, and how it was set. A value must both look like an option AND reach an executable: a named program under a bin directory, /tmp or /dev/shm, a .sh/.py/.pl script path, or a bare interpreter name. An earlier draft accepted any path-shaped value and any `--filter=`; measured against the 3,042-sample skills-sh corpus it fired three times, every hit on Turborepo documentation showing `--filter=./apps/*`. Both the `filter` flag and the loose path acceptance were removed for that reason. `--filter=blob:none` and `--output=/tmp/r.json` are also not matched, by construction.
  THE PARAMETER ANCHOR IS THE RULE (adversarial review, 2026-08-23). The first draft looked for the executor flag ANYWHERE in the content, dropping the positional invariant that the rest of this description argues is the only thing that holds. That version was blind on the 15,175-sample benign corpus and still fired on 16 of 20 hand-written benign inputs: every ordinary operator use of these very flags. `git clone --upload-pack=/usr/local/bin/git-upload-pack` is how you clone from a host with git installed off-prefix; `rsync --rsh=/usr/bin/ssh` is how a cron job with a minimal PATH names its transport; `tar --use-compress-program=/usr/bin/zstd`, `sort --compress-program=/bin/gzip`, `mysql --pager=/usr/bin/less`, `wget --use-askpass=/usr/local/bin/askpass` and ripgrep's own documented `--pre=./scripts/pdf-to-text.sh` are all routine. Worse, a security advisory describing this CVE in prose matched, and so did this rule's own description text. All three conditions are now anchored to a JSON parameter key, which is also the shape production actually produces (the hook builds tool_args as JSON.stringify(toolInput)). The flag in a command line is an operator's choice; the same flag sitting in a slot the schema calls `pattern` is the attack.
  TWO FURTHER NARROWINGS from the same review. Condition 2 previously accepted a space as well as `=`, and accepted a relative `bin/`, so `{"target":"--volume /usr/bin/docker:/usr/bin/docker"}` and `{"glob":"--iglob=bin/**"}` matched; it now requires `=` and an absolute path naming a FILE under bin, which also separates an executor target from an install prefix such as `--bindir=/usr/local/bin/`. Condition 3 previously matched a bare `-exec`, so an agent grepping a repo for that literal string matched; it now requires the `{}` placeholder that makes the value an actual find predicate.
author: ATR Community
date: 2026/08/23
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: high
references:
  cve:
    - CVE-2026-48116
  cwe:
    - CWE-88
    - CWE-77
  owasp_llm:
    - LLM05:2025
  owasp_agentic:
    - ASI02:2026
  mitre_atlas:
    - AML.T0053 - LLM Plugin Compromise
    - AML.T0051.001 - Indirect
compliance:
  owasp_llm:
    - id: LLM05:2025
      context: A model-produced parameter is handed to a process-spawning API without the separator that would keep it an operand; the mishandling is entirely on the output side.
      strength: primary
  owasp_agentic:
    - id: ASI02:2026
      context: 'Tool misuse with no tool compromise: the search tool runs its normal code path, and the argument redefines what that path does.'
      strength: primary
  eu_ai_act:
    - article: '15'
      context: Article 15 requires that a high-risk system resist manipulation of its behaviour; a parameter that silently changes which program a tool executes is exactly such a manipulation.
      strength: primary
    - article: '9'
      context: Argument injection through agent tool parameters is a foreseeable misuse for any agent with filesystem or process tooling and needs the continuous monitoring Article 9 mandates.
      strength: secondary
    - article: '14'
      context: A human approving a search request cannot see that the search pattern is an executor flag; surfacing it restores meaningful oversight.
      strength: secondary
  nist_ai_rmf:
    - function: Manage
      subcategory: MG.2.3
      context: Runtime treatment for argument injection, the tool-invocation failure mode that leaves no shell metacharacter to detect.
      strength: primary
    - function: Measure
      subcategory: MS.2.7
      context: Detection events evidence the security of the parameter-to-process boundary for tools that spawn external binaries.
      strength: secondary
    - function: Map
      subcategory: MP.5.1
      context: Distinguishes argument injection from command injection in the risk map; the mitigations differ (an end-of-options separator, not metacharacter escaping).
      strength: secondary
  iso_42001:
    - clause: '8.1'
      context: Operational control of tool invocation requires that parameters cannot redefine the tool's execution behaviour; this rule detects attempts to do so.
      strength: primary
    - clause: '8.4'
      context: Impact assessment for an agent with search or archive tooling must include argument injection; these detections supply the evidence.
      strength: secondary
    - clause: '6.2'
      context: Preventing unintended program execution through tool parameters is an AIMS security objective, operationalised as a runtime detection.
      strength: secondary
tags:
  category: privilege-escalation
  subcategory: argument-injection
  scan_target: both
  confidence: medium
agent_source:
  type: mcp_exchange
  framework:
    - any
  provider:
    - any
detection:
  condition: any
  conditions:
    - field: content
      operator: regex
      value: (?i)"(?:pattern|query|search_?term|search|term|file_?name|file|member|entry|name|path|directory|dir|target|source|src|destination|dest|glob|regex|expr|domain|hostname|host)"\s*:\s*"\s*--(?:pre-glob|pre|to-command|use-askpass|use-compress-program|compress-program|upload-pack|receive-pack|rsh|pager)\s*=\s*['"]?\s*(?:/(?:usr/)?(?:local/)?s?bin/[a-z0-9_.-]{1,40}|/(?:tmp|var/tmp|dev/shm)/|[~.]{0,2}/[^\s'"]{0,40}\.(?:sh|bash|zsh|py|pl|rb|js|mjs|php)\b|\bsh\b|\bbash\b|\bzsh\b|\bdash\b|\bpython[23]?\b|\bperl\b|\bruby\b|\bnode\b|\benv\b|\bawk\b|\bcurl\b|\bwget\b)
      description: A parameter the tool schema treats as data holds an option known to make its CLI spawn a process, and the option's value reaches an executable — a named program in a bin directory, a world-writable staging directory, a script file, or a bare interpreter name
    - field: content
      operator: regex
      value: (?i)"(?:pattern|query|search_?term|search|term|file_?name|file|member|entry|name|path|directory|dir|target|source|src|destination|dest|glob|regex|expr|domain|hostname|host)"\s*:\s*"\s*--?[a-z][a-z0-9-]{1,24}=\s*['"]?/(?:usr/)?(?:local/)?s?bin/[a-z0-9_.-]{1,40}
      description: A parameter the tool schema treats as data holds an option-shaped value whose payload is an absolute path to a named program in a bin directory — an executor flag wearing an operand's name
    - field: content
      operator: regex
      value: (?i)"(?:pattern|name|glob|path|file_?name|search|query|term)"\s*:\s*"\s*-exec(?:dir)?\s+[^"\\{]{1,40}\{\}
      description: A filename or glob parameter whose value is a complete find -exec predicate, command plus {} placeholder, turning a directory walk into command execution once per matched file
  false_positives:
    - A developer or skill that legitimately passes ripgrep's --pre preprocessor through an agent tool AS the search-pattern parameter (searching inside PDFs or archives). Expected to match; the rule cannot distinguish an intended preprocessor from an injected one once it is sitting in the operand slot
    - An MCP tool whose schema genuinely names its transport parameter `src` or `target` and expects a --rsh= / --use-askpass= value there. Rare — real wrappers give the transport its own parameter — but it would match
    - 'An agent searching a codebase for the literal predicate `-exec rm {}` during a security audit: the value is a complete find predicate under a `pattern` key, which is the detected shape'
    - 'RESIDUAL, and not fixable by pattern: an advisory or writeup that quotes the PoC as a verbatim JSON tool call rather than describing it in prose. The quoted payload is byte-identical to the live attack. Prose descriptions of the attack — the far more common documentation shape, and the one ATR''s own docs use — are clean and are pinned as true_negatives. Code-block suppression (tags.suppress_in_code_blocks) would silence the fenced case but was deliberately NOT enabled: a poisoned SKILL.md or README that carries the malicious tool call inside a fenced block is a live indirect-injection channel, and suppressing it would blind the rule there'
response:
  actions:
    - alert
  actions_rationale: 'response-action eligibility (docs/RESPONSE-ACTION-ELIGIBILITY.md): no FP measurement on the benign corpus; ceiling = observe. Removed block_tool(interrupt). Detection unchanged — recall cost is zero.'
  auto_response_threshold: high
  message_template: |
    [ATR-2026-02528] A tool parameter that should be data is an option flag naming a program. The tool will execute it rather than search for it (CVE-2026-48116 shape). Pass an end-of-options separator before model-controlled operands.
confidence: 70
