title: Language-Agnostic Credential Exfiltration Chain (secret read -> encode -> network send)
id: ATR-2026-02261
rule_version: 2
status: experimental
description: |
  Detects the code-surface (non-shell) form of the credential exfiltration chain that Palo Alto Unit 42's Behavioral Integrity Verification research names as its canonical compound threat: a skill reads a sensitive secret from the environment or a credential file, applies an encoding transform (base64/hex), and transmits it to an outbound network sink -- all in one artifact. ATR's existing exfil-chain rules (ATR-2026-00201/00224) key on shell syntax ("cat ~/.aws/credentials | base64 | curl POST"); this rule covers the equivalent Python/Node/JS behavior ("os.environ / process.env secret -> base64.b64encode / Buffer.toString('base64') -> requests.post / fetch") that shell rules miss. Fires only on the co-occurrence of all three stages, so a benign skill that merely reads an env var, or merely base64-encodes an image, or merely POSTs an event does not match. A second condition covers a script that serializes the whole environment (JSON.stringify(process.env), json.dumps(dict(os.environ))) and sends it, with or without encoding, which the npm packages in OSV MAL-2026-4665 and MAL-2026-10518 did.
author: ATR Community (Unit 42 BIV crosswalk)
date: 2026/07/13
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: critical
references:
  owasp_llm:
    - LLM06:2025
    - LLM03:2025
  owasp_agentic:
    - ASI06:2026
  mitre_atlas:
    - AML.T0051.001 - Indirect
  mitre_attack:
    - T1552.001 - Credentials In Files
    - T1041 - Exfiltration Over C2 Channel
compliance:
  nist_ai_rmf:
    - subcategory: MS.2.10
      context: The rule detects reading of secrets from environment variables or credential files, encoding them, and transmitting them to an external endpoint -- a direct privacy and confidentiality event; MS.2.10 requires assessing and detecting unauthorized disclosure of secrets handled by AI agents.
      strength: primary
    - subcategory: MG.3.2
      context: A skill whose code exfiltrates credentials is a compromised third-party supply-chain component; MG.3.2 requires monitoring integrated third-party skills for malicious behavior post-integration.
      strength: secondary
  iso_42001:
    - clause: '8.1'
      context: ISO/IEC 42001 Clause 8.1 (operational control of externally provided processes) is operationalised by this rule's detection of the skill supply-chain compromise (code-surface credential exfiltration chain).
      strength: primary
    - clause: '8.3'
      context: ISO/IEC 42001 Clause 8.3 (AI risk treatment) is supported by this rule's runtime detection of the code-surface credential exfiltration chain.
      strength: secondary
  eu_ai_act:
    - article: '15'
      context: Article 15 (accuracy, robustness and cybersecurity) requires high-risk AI systems to resist unauthorised attempts to alter outputs or exfiltrate data; this rule provides runtime detection evidence for the code-surface credential exfiltration chain.
      strength: primary
tags:
  category: skill-compromise
  subcategory: credential-exfil-chain
  scan_target: both
  confidence: high
agent_source:
  type: mcp_exchange
  framework:
    - any
  provider:
    - any
detection:
  conditions:
    - field: content
      operator: regex
      value: (?i)(?:^|[^\w$])(?:const\s+|let\s+|var\s+)?([A-Za-z_$][\w$]*)\s*=\s*(?:os\.environ(?:\.get)?\s*[\(\[]\s*['"][A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|PASSWD|CREDENTIAL|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|AUTH_?TOKEN)|process\.env\.[A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|CREDENTIAL|PRIVATE[_-]?KEY|ACCESS[_-]?KEY)|getenv\s*\(\s*['"][A-Z0-9_]*(?:SECRET|TOKEN|API[_-]?KEY|PASSWORD|CREDENTIAL)|(?:open|readFileSync|read_file)\s*\([^)\n]{0,60}(?:\.aws/credentials|\.ssh/id_[a-z0-9]+|\.netrc|credentials\.json))[\s\S]{0,160}?(?:base64\.(?:b64encode|standard_b64encode|encodebytes)\s*\(\s*\1\b|Buffer\.from\s*\(\s*\1\b[^)]*\)\.toString\s*\(\s*['"]base64|btoa\s*\(\s*\1\b|binascii\.(?:hexlify|b2a_base64)\s*\(\s*\1\b|\1\.encode\s*\(\s*['"]base64['"])[\s\S]{0,160}?(?:requests\.(?:post|put|patch)|httpx\.(?:post|put)|urllib\.request\.urlopen|urllib2\.urlopen|http\.client\.HTTPS?Connection|aiohttp\.[A-Za-z_]*\.(?:post|put)|fetch\s*\(|axios\.(?:post|put)|\.sendall\s*\(|\.sendto\s*\()
      description: 'Ordered chain: secret read into a variable -> that SAME variable base64/hex-encoded (backreference-bound) -> outbound network send, within a bounded window. The backreference (\1) is what keeps the encode stage tied to the secret: a skill that reads a token for SDK auth but base64-encodes an UNRELATED image/file and POSTs it (a very common legitimate upload pattern) no longer matches, because the encoded operand is a different variable than the one holding the secret.'
    - field: content
      operator: regex
      value: (?i)(?:(?:\bfetch\s*\(|\baxios(?:\.(?:get|post|put|request))?\s*\(|\bhttps?\.(?:request|get)\s*\(|\bnavigator\.sendBeacon\s*\(|\bnew\s+WebSocket\s*\(|\brequests\.(?:get|post|put|patch)\s*\(|\burlopen\s*\(|\bhttpx\.(?:get|post|put)\s*\(|\.send(?:all|to)\s*\(|\b(?:ws|socket|sock)\.send\s*\()[^;\n]{0,300}?(?:JSON\.stringify|json\.dumps)\s*\(\s*(?:\{[^{}()]{0,120}?)?(?:process\.env|(?:dict\s*\(\s*)?os\.environ(?:\.copy\s*\(\s*\))?)\s*[,})]|(?:JSON\.stringify|json\.dumps)\s*\(\s*(?:\{[^{}()]{0,120}?)?(?:process\.env|(?:dict\s*\(\s*)?os\.environ(?:\.copy\s*\(\s*\))?)\s*[,})][\s\S]{0,300}?(?:(?:Buffer\.from\s*\([^()]{0,80}\)\s*\.toString\s*\(\s*['"](?:base64|hex)|\bb64encode\s*\(|\bbtoa\s*\(|\bhexlify\s*\()[\s\S]{0,300}?(?:\bfetch\s*\(|\baxios(?:\.(?:get|post|put|request))?\s*\(|\bhttps?\.(?:request|get)\s*\(|\bnavigator\.sendBeacon\s*\(|\bnew\s+WebSocket\s*\(|\brequests\.(?:get|post|put|patch)\s*\(|\burlopen\s*\(|\bhttpx\.(?:get|post|put)\s*\(|\.send(?:all|to)\s*\(|\b(?:ws|socket|sock)\.send\s*\()|(?:\bfetch\s*\(|\baxios(?:\.(?:get|post|put|request))?\s*\(|\bhttps?\.(?:request|get)\s*\(|\bnavigator\.sendBeacon\s*\(|\bnew\s+WebSocket\s*\(|\brequests\.(?:get|post|put|patch)\s*\(|\burlopen\s*\(|\bhttpx\.(?:get|post|put)\s*\(|\.send(?:all|to)\s*\(|\b(?:ws|socket|sock)\.send\s*\()[^;\n]{0,300}?(?:Buffer\.from\s*\([^()]{0,80}\)\s*\.toString\s*\(\s*['"](?:base64|hex)|\bb64encode\s*\(|\bbtoa\s*\(|\bhexlify\s*\())|(?:Buffer\.from|b64encode|btoa|hexlify)\s*\(\s*(?:str\s*\(\s*)?(?:(?:JSON\.stringify|json\.dumps)\s*\(\s*(?:\{[^{}()]{0,120}?)?(?:process\.env|(?:dict\s*\(\s*)?os\.environ(?:\.copy\s*\(\s*\))?)\s*[,})]|(?:process\.env|(?:dict\s*\(\s*)?os\.environ(?:\.copy\s*\(\s*\))?)\s*\))[\s\S]{0,400}?(?:\bfetch\s*\(|\baxios(?:\.(?:get|post|put|request))?\s*\(|\bhttps?\.(?:request|get)\s*\(|\bnavigator\.sendBeacon\s*\(|\bnew\s+WebSocket\s*\(|\brequests\.(?:get|post|put|patch)\s*\(|\burlopen\s*\(|\bhttpx\.(?:get|post|put)\s*\(|\.send(?:all|to)\s*\(|\b(?:ws|socket|sock)\.send\s*\()|(?:\bfetch\s*\(|\baxios(?:\.(?:get|post|put|request))?\s*\(|\bhttps?\.(?:request|get)\s*\(|\bnavigator\.sendBeacon\s*\(|\bnew\s+WebSocket\s*\(|\brequests\.(?:get|post|put|patch)\s*\(|\burlopen\s*\(|\bhttpx\.(?:get|post|put)\s*\(|\.send(?:all|to)\s*\(|\b(?:ws|socket|sock)\.send\s*\()[\s\S]{0,400}?(?:Buffer\.from|b64encode|btoa|hexlify)\s*\(\s*(?:str\s*\(\s*)?(?:(?:JSON\.stringify|json\.dumps)\s*\(\s*(?:\{[^{}()]{0,120}?)?(?:process\.env|(?:dict\s*\(\s*)?os\.environ(?:\.copy\s*\(\s*\))?)\s*[,})]|(?:process\.env|(?:dict\s*\(\s*)?os\.environ(?:\.copy\s*\(\s*\))?)\s*\)))
      description: 'The whole environment serialized (JSON.stringify(process.env), json.dumps(dict(os.environ)), or the env object base64/hex-encoded directly), and reaching the network: serialized inside the send call itself, serialized and then encoded and sent, or wrapped directly in a base64/hex encoder near a send. This is how the npm packages in OSV MAL-2026-4665, MAL-2026-10518 and the emails-helper report sent every variable at once, where the first condition needs one secret read into a variable. Passing process.env to a child process (env: process.env, or a spread) is not serialization and does not match.'
  condition: any
  false_positives:
    - Security training or documentation describing the exfiltration chain in prose (no live code primitives co-occur)
    - A skill that reads a secret for legitimate SDK auth without encoding + outbound POST of that secret
    - A debugging or support tool that sends the full environment to its own server on purpose; this is rare, and doing so leaks every secret on the host anyway.
    - Base64-encoding of non-secret data (images, payloads) unaccompanied by a secret read
    - 'A skill that reads a secret purely for an auth header AND separately base64-encodes an unrelated media/file payload before uploading it (image-upload, OCR, crash-report skills) — the three primitives co-occur but the encoded value is not the secret. Excluded via backreference binding: the encode stage must operate on the same variable that received the secret.'
response:
  actions:
    - alert
    - block_tool
  auto_response_threshold: high
  message_template: |
    [ATR-2026-02261] Code-surface credential exfiltration chain detected: secret read -> encode -> outbound network send co-occur in one skill artifact.
