title: Agent HTTP Request Claims a Loopback Origin While Targeting an External Host
id: ATR-2026-02708
rule_version: 2
status: experimental
description: |
  Detects an agent HTTP tool call that sends a request to a NAMED external host while declaring a loopback origin in a header the server is likely to trust -- `Host: 127.0.0.1`, `X-Forwarded-For: 127.0.0.1`, `X-Real-IP: localhost`, `X-Originating-IP: ::1`. Mined from CVE-2026-61435 (PraisonAI), whose bind-host authorization check derived "is this request local?" from `request.url.hostname`, i.e. from the Host header the caller supplies, so an internet-reachable deployment granted localhost-only privileges to anyone who asked for them. Verified on the shipped engine: two payload forms across all five event types matched nothing, and a grep of rules/ finds no Host/X-Forwarded-For spoofing rule at all. RULE_VERSION 2, after adversarial review fired version 1 on 6 of 22 benign probes. Three constraints were added, each forced by a probe. (1) THE CALL MUST BE REQUEST-ISSUING. Version 1 checked only the argument text, so an agent writing a penetration-test note through write_file -- `ffuf -u https://target.example/admin -H "X-Forwarded-For: 127.0.0.1"` -- matched, because the payload was in the file body. Code-block suppression, which version 1 relied on for exactly this sample, only helps when the example is fenced, and a note file is not. (2) `host` NEEDS A REAL HEADER CONTEXT. Version 1 accepted any QUOTED key named host, and its false-positive note claimed configuration files were excluded by that quoting requirement. They are not: JSON quotes its keys, so `{"tool":"db_query","arguments":{"host":"localhost","schema_url":"https://schemas.corp.example/..."}}` matched. `host` now requires -H, --header, a headers object, or an HTTP wire line. The `x-*` names keep the looser form, since nothing else is called x-forwarded-for. (3) THE TWO HALVES MUST BE ADJACENT AND IN THE SAME STATEMENT. Version 1 only asked that both appear somewhere in the arguments, so `curl -H 'Host: localhost' http://127.0.0.1:8080/health; curl https://status.corp.example/ping` satisfied them from two different commands. The gap is now 120 characters with no `;`, no newline, no `://`, no pipe and no `&` -- the last four because every remaining false positive was a loopback header attached to a LOOPBACK request, with an unrelated external URL in a trailing comment, an echo, or the other half of a pipeline. The pairing remains the precision handle: a request TO 127.0.0.1 carrying a virtual-host `Host:` header is ordinary local testing and does not match, because the external-URL condition demands a hostname ending in an alphabetic TLD, which no dotted-quad or bare `localhost` target satisfies.
author: ATR Community (CVE sweep)
date: 2026/08/23
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: high
references:
  cwe:
    - CWE-290
    - CWE-350
  owasp_llm:
    - LLM06:2025
  owasp_agentic:
    - ASI06:2026
  mitre_attack:
    - T1078 - Valid Accounts
  mitre_atlas:
    - AML.T0053 - LLM Plugin Compromise
  cve:
    - CVE-2026-61435
  external:
    - https://nvd.nist.gov/vuln/detail/CVE-2026-61435
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 use; this rule detects an agent request forging a loopback origin to satisfy a localhost-only authorization check on a remote service.
      strength: primary
    - article: '9'
      context: Article 9 (risk management system) requires identified risks to be treated; this rule is the runtime treatment for header-derived trust decisions in agent-reachable services.
      strength: secondary
  nist_ai_rmf:
    - subcategory: MG.2.3
      context: Authorization bypass via a spoofed origin header is an identified AI risk requiring an active runtime countermeasure; this rule implements it.
      strength: primary
    - subcategory: MP.5.1
      context: Cataloguing caller-controlled origin headers as an AI-specific authorization risk 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 detecting forged origin headers before the request is issued.
      strength: primary
    - clause: '8.3'
      context: ISO/IEC 42001 Clause 8.3 (AI risk treatment) is implemented through runtime detection of the spoofed header.
      strength: secondary
tags:
  category: privilege-escalation
  subcategory: forged-loopback-origin-header
  scan_target: runtime
  confidence: high
  suppress_in_code_blocks: true
agent_source:
  type: tool_call
  framework:
    - any
  provider:
    - any
detection:
  condition: all
  conditions:
    - field: tool_args
      operator: regex
      value: (?:["\x27](?:tool|tool_name|toolName|name|function|mcp_tool)["\x27]\s{0,3}:\s{0,3}["\x27][\w.\-]{0,40}(?:http|curl|wget|fetch|request|req|api|webhook|url|browse|axios|httpx)[\w.\-]{0,20}["\x27]|["\x27](?:command|cmd|script|shell|args|argv)["\x27]\s{0,3}:\s{0,3}[\s\S]{0,20}\b(?:curl|wget|httpie|\bxh\b|invoke-webrequest)\b)
      description: The call is issued by an HTTP-request tool, or by a shell tool running an HTTP client
    - field: tool_args
      operator: regex
      value: (?:https?://[a-z0-9-]{2,30}(?:\.[a-z0-9-]{1,30}){0,4}\.[a-z]{2,12}(?:[:/?"\x27]|\s|$)(?:[^:;\n|&]|:[^/;\n|&]|:/[^/;\n|&]){0,120}(?:(?:-H\s{1,3}["\x27]?|--header\s{1,3}["\x27]?|\bheaders?\b["\x27]?\s{0,3}[:=]?\s{0,3}[{(\[]?\s{0,3}["\x27]?)host["\x27]?\s{0,3}[:=]\s{0,3}["\x27]?\s{0,2}(?:127\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost|\[?::1\]?|0\.0\.0\.0)\b|(?:^|[\r\n])\s{0,3}host["\x27]?\s{0,3}[:=]\s{0,3}["\x27]?\s{0,2}(?:127\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost|\[?::1\]?|0\.0\.0\.0)\b|(?:["\x27]|(?:-H\s{1,3}["\x27]?|--header\s{1,3}["\x27]?|\bheaders?\b["\x27]?\s{0,3}[:=]?\s{0,3}[{(\[]?\s{0,3}["\x27]?))(?:x-forwarded-for|x-real-ip|x-originating-ip|x-client-ip|x-forwarded-host)["\x27]?\s{0,3}[:=]\s{0,3}["\x27]?\s{0,2}(?:127\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost|\[?::1\]?|0\.0\.0\.0)\b)|(?:(?:-H\s{1,3}["\x27]?|--header\s{1,3}["\x27]?|\bheaders?\b["\x27]?\s{0,3}[:=]?\s{0,3}[{(\[]?\s{0,3}["\x27]?)host["\x27]?\s{0,3}[:=]\s{0,3}["\x27]?\s{0,2}(?:127\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost|\[?::1\]?|0\.0\.0\.0)\b|(?:^|[\r\n])\s{0,3}host["\x27]?\s{0,3}[:=]\s{0,3}["\x27]?\s{0,2}(?:127\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost|\[?::1\]?|0\.0\.0\.0)\b|(?:["\x27]|(?:-H\s{1,3}["\x27]?|--header\s{1,3}["\x27]?|\bheaders?\b["\x27]?\s{0,3}[:=]?\s{0,3}[{(\[]?\s{0,3}["\x27]?))(?:x-forwarded-for|x-real-ip|x-originating-ip|x-client-ip|x-forwarded-host)["\x27]?\s{0,3}[:=]\s{0,3}["\x27]?\s{0,2}(?:127\.\d{1,3}\.\d{1,3}\.\d{1,3}|localhost|\[?::1\]?|0\.0\.0\.0)\b)(?:[^:;\n|&]|:[^/;\n|&]|:/[^/;\n|&]){0,120}https?://[a-z0-9-]{2,30}(?:\.[a-z0-9-]{1,30}){0,4}\.[a-z]{2,12}(?:[:/?"\x27]|\s|$))
      description: A named external host and a loopback-valued origin header, adjacent and in the same statement
  false_positives:
    - 'A deliberate penetration test replaying a webhook with X-Forwarded-For: 127.0.0.1 against a staging host. This matches, and matching is correct: the act being performed is the bypass, whoever is performing it.'
    - 'Local virtual-host testing (`curl -H ''Host: app.example.com'' http://127.0.0.1:8080/`), where the loopback address is the TARGET and the header carries a real name. Neither half matches.'
    - A reverse-proxy configuration using `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for`, since the value is a variable rather than a loopback literal.
    - Configuration that binds a service locally while naming a remote API -- `{"host":"localhost"}` beside `https://api.corp.example`, `- HOST=0.0.0.0` in a compose file, a local debugging proxy on a request to an external API. All of these FIRED version 1; `host` now needs a real header context.
    - Security-tool documentation whose examples contain the payload, fenced or not. The rule now also requires the call itself to be request-issuing, so writing the ffuf line into a notes file does not match.
response:
  actions:
    - alert
  message_template: |
    [ATR-2026-02708] Agent request targets a named external host while declaring a loopback origin (Host / X-Forwarded-For / X-Real-IP). Derive locality from the socket, never from a caller-supplied header.
confidence: 80
