title: Reverse Shell Assembled In-Source (socket bound to an interactive shell)
id: ATR-2026-02600
rule_version: 1
status: experimental
description: |
  Detects a reverse shell written as SOURCE CODE rather than as a shell one-liner: an outbound socket is opened to a raw host/port, the process's standard file descriptors are rebound onto that socket (or piped bidirectionally into a child), and an INTERACTIVE shell is handed the result. This is the form malicious PyPI / npm packages ship in (OSSF MAL-2025-191647 mcp-runcommand-server and siblings), and it is exactly the form ATR-2026-00121 cannot see: 00121's reverse-shell condition is written as `python[3]?\s+-c\s+['"]import\s+(socket|os)` plus `bash -i >& /dev/tcp/` and `nc -e`, i.e. CLI invocations only. A package whose __init__.py contains the same behaviour never matches.
  WHY THREE CONDITIONS, ALL REQUIRED. Every single element is common in legitimate code. `socket.connect` is every HTTP client. `os.dup2(f.fileno(), 1)` is every daemoniser and every log-redirecting supervisor. `spawn('/bin/sh', ...)` is every build script. Measured against hand-written benign twins, four of them matched two of the three conditions -- a node log shipper (net.connect + client.pipe(tailer.stdin) + spawn('/bin/sh', ['-c', 'tail -F ...'])), a python journal shipper (socket.connect + os.dup2(s.fileno(), 1) + subprocess.call(['/bin/sh','-c','journalctl -f'])), a CI runner and a TCP relay. Those are the shapes this rule has to survive, and the reason condition 3 does not accept a shell that is given a `-c` command: shipping the output of a NAMED command over a socket is ordinary operations; handing over a shell with no command, or with `-i`, or through pty.spawn, is not.
  DELIBERATELY NOT COVERED. Listening (bind/listen) shells are excluded -- condition 1 requires an outbound connect, because web-terminal backends (ttyd/gotty/xterm.js, pty.fork + execvp('/bin/bash')) are the benign twin of a bind shell and are indistinguishable at the pattern layer. No C2 address, port or package name is hardcoded; the shape is the signal.
author: ATR Community
date: 2026/08/23
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: critical
references:
  owasp_llm:
    - LLM03:2025
  owasp_agentic:
    - ASI05:2026
  mitre_atlas:
    - AML.T0010 - AI Supply Chain Compromise
    - AML.T0011 - User Execution
  cve:
    - MAL-2025-191647
compliance:
  owasp_agentic:
    - id: ASI05:2026
      context: A dependency or skill an agent loads opens an attacker-controlled channel and hands over an interactive shell, which is unbounded remote control of the agent host.
      strength: primary
  owasp_llm:
    - id: LLM03:2025
      context: 'Supply-chain compromise detection: the reverse shell arrives inside a package the agent installed or a skill it loaded, not through the prompt.'
      strength: primary
  eu_ai_act:
    - article: '15'
      context: Article 15 requires accuracy, robustness and cybersecurity for high-risk systems; an interactive shell bound to an external socket is total loss of control over the system, and this rule is the detection control for it.
      strength: primary
    - article: '9'
      context: Malicious dependency execution is a documented risk class for agent runtimes; detection events from this rule are the monitoring evidence Article 9 risk management requires.
      strength: secondary
    - article: '12'
      context: 'Article 12 logging: a match records the exact source text that established the channel, which is the record an incident reconstruction needs.'
      strength: secondary
  nist_ai_rmf:
    - function: Manage
      subcategory: MG.2.3
      context: 'Runtime treatment for third-party component risk: the component under evaluation contains remote-control code.'
      strength: primary
    - function: Map
      subcategory: MP.5.1
      context: Catalogues in-source reverse shells as a distinct supply-chain risk from shell-command reverse shells, which existing coverage already addresses.
      strength: secondary
    - function: Measure
      subcategory: MS.2.7
      context: Detection events supply the security and resilience measurement MEASURE 2.7 asks to be documented for agent dependencies.
      strength: secondary
  iso_42001:
    - clause: '8.1'
      context: 'Clause 8.1 control of externally provided processes and products: a package that opens a shell to an external host is an externally provided process operating outside any defined boundary.'
      strength: primary
    - clause: '6.2'
      context: Detecting remote-control code in loaded components is an AIMS information security objective under clause 6.2, operationalised as a runtime control.
      strength: secondary
    - clause: '8.4'
      context: Impact assessment under 8.4 must account for third-party components; these events are the monitoring evidence for the component-compromise path.
      strength: secondary
tags:
  category: skill-compromise
  subcategory: reverse-shell
  scan_target: both
  confidence: high
  suppress_in_code_blocks: true
agent_source:
  type: mcp_exchange
  framework:
    - any
  provider:
    - any
detection:
  conditions:
    - field: content
      operator: regex
      value: (?:\bsocket\.socket\s*\(\s*(?:socket\.)?AF_INET[\s\S]{0,120}?\.connect\s*\(|\.connect\s*\(\s*\(\s*["'][\w.\-]{1,60}["']\s*,\s*\d{1,5}\s*\)|\bnet\.(?:connect|createConnection)\s*\(\s*\d{1,5}\s*,|new\s+net\.Socket\s*\([\s\S]{0,80}\.connect\s*\(\s*\d{1,5})
      description: Outbound socket opened to a literal host and port (python socket / node net)
    - field: content
      operator: regex
      value: (?:\bdup2\s*\(\s*[\w.\[\]()]{1,24}\.fileno\s*\(\s*\)\s*,\s*[0-2]\s*\)|\.pipe\s*\(\s*[\w.$]{1,24}\.stdin\s*\)|\bstdin\s*=\s*[\w.$]{1,24}\.fileno\s*\(\s*\))
      description: Process stdio rebound onto a socket descriptor, or bidirectional pipe into a child's stdin
    - field: content
      operator: regex
      value: (?:\bpty\.spawn\s*\(|\bsubprocess\.(?:call|run|Popen|check_call)\s*\(\s*\[\s*["']/bin/(?:sh|bash|zsh|dash|ash)["']\s*(?:,\s*["']-i["']\s*)?\]|\bos\.exec(?:l|v|le|ve|lp|vp|lpe|vpe)?\s*\(\s*["']/bin/(?:sh|bash|zsh|dash)["']|\b(?:spawn|execFile)\s*\(\s*["'](?:/bin/)?(?:sh|bash|zsh|dash)["']\s*(?:\)|,\s*\[\s*\]))
      description: 'Interactive shell handover: no command argument, an explicit -i, or pty.spawn'
  condition: all
  false_positives:
    - Security research, malware write-ups and detection-engineering notes that quote a complete reverse-shell source listing verbatim. The rule cannot distinguish a payload from a payload being explained, and does not try; triage the surrounding context
    - Purpose-built remote administration or remote-shell tooling (an SSH-alternative, a jump-host agent) whose whole job is this shape. Legitimate by intent, identical by structure
    - 'Exploit-development, GTFOBins-style and CTF material stored in a repository the agent reads. Measured on the benign corpus: a penetration-testing skill document quoting the canonical python reverse shell fired this rule until tags.suppress_in_code_blocks was set, which drops matches that occur inside a fenced code block. Payloads arriving as an actual file body or tool argument are not fenced and are unaffected'
response:
  actions:
    - alert
    - snapshot
    - escalate
  auto_response_threshold: critical
  message_template: |
    [ATR-2026-02600] Source code detected that opens an outbound socket, rebinds the process's standard descriptors onto it, and hands over an interactive shell -- a reverse shell in library form rather than as a shell command. Treat the containing package, skill or file as compromised until reviewed.
confidence: 85
