title: Branch or Tag Ref Name Carrying Shell Command Substitution in Tool Output
id: ATR-2026-02668
rule_version: 2
status: experimental
description: |
  Detects a version-control REF NAME that contains a shell command substitution: a branch listed as remotes/origin/hotfix-$(id > /tmp/pwned), or a head_ref / branch / tag_name field holding $(cat ~/.ssh/id_rsa | base64). Git allows those characters in a ref name, and everything downstream that interpolates the name into a shell -- a prompt renderer, a release script, a GitHub Actions run: block using github.head_ref -- executes it. For an agent the decisive event is the tool_response: git branch -a or gh pr list output that the model then feeds into its next command. Mined from CVE-2026-27113 (Liquid Prompt). ADVERSARIAL REVIEW 2026-08-24. Fifteen benign samples were run through the shipped engine against rule_version 1; FOURTEEN fired. Two changes, and one arm cut back to nothing:
  (1) THE PR-TITLE FORM WAS ABANDONED. `title` is no longer in the field list. Rule_version 1 had already been narrowed once during authoring -- from "any pipe" to "shell, encoder, fetch tool or system redirect" -- and it still fired on ten ordinary pull-request titles, because those are exactly the tools a title about shell scripting names: "fix: quote $(curl -sSf https://sh.rustup.rs) in the bootstrap script", "ci: replace $(base64 -w0 tls.crt) with a projected secret volume", "chore: drop $(eval echo ~) in favour of $HOME in the installer", "docs: warn that $(curl -fsSL url | sh) installers cannot be verified", and -- the diagnostic one -- the title of the ATR pull request that ships this very rule. A PR title is prose: it may contain any command substitution, with any payload, for any reason. There is no shape that separates a poisoned title from a title about poisoning, so the title form was removed rather than narrowed a third time. Ref-NAME fields (head_ref / headRefName / branch / tag_name / source_branch) are kept: those are identifiers, not prose. STATED RECALL LIMIT: a poisoned PR title is not detected by this rule at all.
  (2) THE LISTING FORM NOW REQUIRES A PAYLOAD AND AN END-OF-LINE. Version 1 matched any `$(` after `origin/<something>` at the start of a line, which is the shape of a wrapped shell command, not only of listing output. It fired on a multi-line `for ref in \\ origin/main \\ origin/release-$(VERSION)` loop, on a Makefile continuation `origin/nightly-$(BUILD_TAG)...HEAD`, and on a documentation snippet giving `origin/nightly-$(date +%Y%m%d)` as a naming convention. Real `git branch -a` output has nothing after the ref, and a poisoned ref carries a payload, so both are now required. `$(VERSION)`, `$(BUILD_TAG)` and `$(date +%Y%m%d)` are all silent.
author: ATR Community (CVE sweep)
date: 2026/08/23
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: high
references:
  cve:
    - CVE-2026-27113
  cwe:
    - CWE-78
    - CWE-88
  owasp_llm:
    - LLM01:2025
    - LLM05:2025
  owasp_agentic:
    - ASI04:2026
    - ASI05:2026
  mitre_attack:
    - T1059.004 - Unix Shell
    - T1195.001 - Compromise Software Dependencies and Development Tools
  mitre_atlas:
    - AML.T0051 - LLM Prompt Injection
  external:
    - https://nvd.nist.gov/vuln/detail/CVE-2026-27113
metadata_provenance:
  cve: human-reviewed
  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 resilience against attacker-controlled input; a repository ref name is attacker-controlled input, and this rule detects the form that executes.
      strength: primary
    - article: '9'
      context: 'Article 9 (risk management system): command injection through repository metadata is an identified risk treated at runtime by this detection.'
      strength: secondary
  nist_ai_rmf:
    - subcategory: MG.2.3
      context: Poisoned repository metadata reaching an agent's shell is an identified AI risk; this rule is the runtime countermeasure on the channel that carries it.
      strength: primary
    - subcategory: MP.5.1
      context: Cataloguing ref-name command injection in the organizational AI risk register.
      strength: secondary
  iso_42001:
    - clause: '8.1'
      context: ISO/IEC 42001 Clause 8.1 (operational planning and control) is operationalised by inspecting tool output for identifiers that will execute when interpolated.
      strength: primary
    - clause: '8.3'
      context: ISO/IEC 42001 Clause 8.3 (AI risk treatment) is implemented by alerting before the agent reuses the ref name in a command.
      strength: secondary
tags:
  category: tool-poisoning
  subcategory: ref-name-command-injection
  scan_target: runtime
  confidence: high
agent_source:
  type: mcp_exchange
  framework:
    - any
  provider:
    - any
detection:
  condition: any
  conditions:
    - field: content
      operator: regex
      value: (?:\n|\\n|^)\s{0,6}\*?\s{0,4}(?:remotes/)?origin/[\w.\-/]{1,40}\$\([^)\r\n]{0,60}(?:\|\s{0,3}(?:sh|bash|zsh|base64|nc|curl|wget|python\d{0,1}|perl)\b|>\s{0,3}/(?:tmp|dev|var|etc|home|root|Users)/|\b(?:curl|wget|nc|chmod|base64|eval)\b|\brm\s{1,3}-[rf])[^)\r\n]{0,60}\)\s{0,4}(?:\r?\n|\\n|$)
      description: 'A ref-listing line (git branch -a / git branch -r output) whose branch name carries a command substitution that pipes into a shell or encoder, redirects into a system directory, or names a fetch/permission tool, and which ENDS at the ref -- real listing output has nothing after the ref. NARROWED by adversarial review 2026-08-24: see false_positives.'
    - field: content
      operator: regex
      value: '["''](?:head_ref|headRefName|ref|branch|tag_name|source_branch|refName|base_ref|baseRefName)["'']\s{0,3}:\s{0,3}["''][^"''\n]{0,60}\$\([^)\n]{0,60}(?:\|\s{0,3}(?:sh|bash|zsh|base64|nc|curl|wget|python\d{0,1}|perl)\b|>\s{0,3}/(?:tmp|dev|var|etc|home|root|Users)/|\b(?:curl|wget|nc|chmod|base64|eval)\b|\brm\s{1,3}-[rf])'
      description: A ref-NAME field (head_ref / headRefName / branch / tag_name / source_branch / ref) whose value carries a command substitution with a shell, encoder, fetch tool or system-directory redirect inside it. `title` was REMOVED from the field list by adversarial review 2026-08-24 -- see false_positives.
  false_positives:
    - 'Shell commands that interpolate a real ref: git rev-parse origin/$(git branch --show-current), DIFF=$(git diff origin/main...HEAD), git push origin HEAD:refs/heads/build-$(date +%Y%m%d). None is a ref-listing line, so none matches.'
    - 'A wrapped shell command or Makefile continuation whose next line begins with the ref: `for ref in \ origin/main \ origin/release-$(VERSION); do`, `git diff --name-only \ origin/nightly-$(BUILD_TAG)...HEAD`. MEASURED: both fired rule_version 1. The line must now END at the ref and the substitution must carry a payload.'
    - 'Documentation giving a branch-naming convention such as origin/nightly-$(date +%Y%m%d). MEASURED: fired rule_version 1; $(date ...) is not a payload.'
    - PULL-REQUEST AND ISSUE TITLES OF EVERY KIND, including poisoned ones. `title` was removed from the field list -- see change (1) in the description. Ten benign titles fired rule_version 1, among them the title of the ATR pull request shipping this rule.
    - An ordinary git branch -a listing, including branches with slashes and dots.
    - A payload with no redirect, pipe or known tool inside the substitution is NOT detected. Stated limit.
    - 'KNOWN AND UNFIXABLE, MEASURED: a blog post or advisory that reproduces a poisoned listing line verbatim still fires, because the sample contains the attack. Prose describing the technique without the listing line is silent.'
response:
  actions:
    - alert
  message_template: |
    [ATR-2026-02668] A branch, tag or pull-request identifier in this tool output contains a shell command substitution. Do not interpolate it into a command line; quote it or reject the ref.
confidence: 80
