Package Runtime Secret File Access Followed by IMDS Request


Description

Identifies a Python, Node.js, Bun, Deno, Ruby, PHP or Perl runtime, or a binary executing from a package install location, that opens a developer secret (SSH keys, package registry tokens, source control credentials or AI coding assistant credentials) and then connects to a cloud instance metadata service (IMDS) endpoint within one minute. This sequence matches credential stealers delivered through compromised packages, such as the malicious LiteLLM PyPI releases, while cloud provider credential files are excluded because legitimate cloud SDK credential chains read them before falling back to IMDS.

Query · eql

sequence by process.entity_id with maxspan=1m
  [file where event.action == "open" and host.os.type in ("macos", "windows") and
    file.path like~ (
      /* SSH private keys */
      "/Users/*/.ssh/id_*", "?:\\Users\\*\\.ssh\\id_*",

      /* Package registry, source control and secrets manager tokens */
      "/Users/*/.npmrc", "?:\\Users\\*\\.npmrc",
      "/Users/*/.pypirc", "?:\\Users\\*\\.pypirc",
      "/Users/*/.git-credentials", "?:\\Users\\*\\.git-credentials",
      "/Users/*/.config/git/credentials", "?:\\Users\\*\\.config\\git\\credentials",
      "/Users/*/.config/gh/hosts.yml", "?:\\Users\\*\\AppData\\Roaming\\GitHub CLI\\hosts.yml",
      "/Users/*/.vault-token", "?:\\Users\\*\\.vault-token",
      "/Users/*/.terraform.d/credentials.tfrc.json", "?:\\Users\\*\\AppData\\Roaming\\terraform.d\\credentials.tfrc.json",

      /* Additional package registry and AI/ML platform tokens */
      "/Users/*/.yarnrc.yml", "?:\\Users\\*\\.yarnrc.yml",
      "/Users/*/.gem/credentials", "?:\\Users\\*\\.gem\\credentials",
      "/Users/*/.cargo/credentials.toml", "?:\\Users\\*\\.cargo\\credentials.toml",
      "/Users/*/.composer/auth.json", "/Users/*/.config/composer/auth.json", "?:\\Users\\*\\AppData\\Roaming\\Composer\\auth.json",
      "/Users/*/.cache/huggingface/token", "/Users/*/.huggingface/token", "?:\\Users\\*\\.cache\\huggingface\\token", "?:\\Users\\*\\.huggingface\\token",
      "/Users/*/.config/github-copilot/apps.json", "/Users/*/.config/github-copilot/hosts.json",
      "?:\\Users\\*\\AppData\\Local\\github-copilot\\apps.json", "?:\\Users\\*\\AppData\\Local\\github-copilot\\hosts.json",
      "/Users/*/.ollama/id_ed25519", "?:\\Users\\*\\.ollama\\id_ed25519",

      /* AI coding assistant credentials */
      "/Users/*/.claude/.credentials.json", "?:\\Users\\*\\.claude\\.credentials.json",
      "/Users/*/.codex/auth.json", "?:\\Users\\*\\.codex\\auth.json",
      "/Users/*/.codex/.credentials.json", "?:\\Users\\*\\.codex\\.credentials.json",
      "/Users/*/.codex/secrets/*.age", "?:\\Users\\*\\.codex\\secrets\\*.age",
      "/Users/*/.gemini/oauth_creds.json", "?:\\Users\\*\\.gemini\\oauth_creds.json",
      "/Users/*/.gemini/gemini-credentials.json", "?:\\Users\\*\\.gemini\\gemini-credentials.json",
      "/Users/*/.gemini/mcp-oauth-tokens.json", "?:\\Users\\*\\.gemini\\mcp-oauth-tokens.json",
      "/Users/*/.gemini/a2a-oauth-tokens.json", "?:\\Users\\*\\.gemini\\a2a-oauth-tokens.json",
      "/Users/*/.qwen/oauth_creds.json", "?:\\Users\\*\\.qwen\\oauth_creds.json",
      "/Users/*/.qwen/mcp-oauth-tokens.json", "?:\\Users\\*\\.qwen\\mcp-oauth-tokens.json",
      "/Users/*/.aws/sso/cache/kiro-auth-token.json", "?:\\Users\\*\\.aws\\sso\\cache\\kiro-auth-token.json",
      "/Users/*/.local/share/opencode/auth.json", "?:\\Users\\*\\.local\\share\\opencode\\auth.json",
      "/Users/*/.local/share/opencode/mcp-auth.json", "?:\\Users\\*\\.local\\share\\opencode\\mcp-auth.json",
      "/Users/*/.local/share/amp/secrets.json", "?:\\Users\\*\\.local\\share\\amp\\secrets.json",
      "/Users/*/.augment/session.json", "?:\\Users\\*\\.augment\\session.json",
      "/Users/*/.kimi/credentials/kimi-code.json", "?:\\Users\\*\\.kimi\\credentials\\kimi-code.json",
      "/Users/*/.cursor/auth.json", "?:\\Users\\*\\.cursor\\auth.json",
      "/Users/*/.pi/agent/auth.json", "?:\\Users\\*\\.pi\\agent\\auth.json",
      "/Users/*/.config/goose/secrets.yaml", "?:\\Users\\*\\AppData\\Roaming\\Block\\goose\\config\\secrets.yaml",
      "/Users/*/.config/muse/auth.json", "?:\\Users\\*\\.config\\muse\\auth.json",
      "/Users/*/.local/share/devin/credentials.toml", "?:\\Users\\*\\AppData\\Roaming\\devin\\credentials.toml",
      "/Users/*/.openclaw/credentials/oauth.json", "?:\\Users\\*\\.openclaw\\credentials\\oauth.json",
      "/Users/*/.openclaw/agents/*/agent/auth-profiles.json", "?:\\Users\\*\\.openclaw\\agents\\*\\agent\\auth-profiles.json",
      "/Users/*/.openclaw/agents/*/agent/auth.json", "?:\\Users\\*\\.openclaw\\agents\\*\\agent\\auth.json"
    ) and

    /* SSH public keys are not secrets and are routinely read by SSH and cloud tooling */
    not file.path like~ "*.pub" and

    /* Package ecosystem runtimes or binaries executing from package install locations */
    (
      process.name like~ ("python*", "node", "node.exe", "nodejs", "bun", "bun.exe", "deno", "deno.exe", "ruby*", "php*", "perl*") or
      process.executable like~ ("*/site-packages/*", "*/dist-packages/*", "*/node_modules/*", "*\\site-packages\\*", "*\\node_modules\\*",
                                "*/.venv/*", "*/venv/*", "*\\.venv\\*", "*\\venv\\*")
    )
  ]
  [network where
    cidrmatch(destination.ip, "169.254.169.254/32", "169.254.170.2/32", "169.254.170.23/32", "100.100.100.200/32", "fd00:ec2::254/128", "fd00:ec2::23/128") and
    destination.port == 80 and event.action == "connection_attempted" and host.os.type in ("macos", "windows")
  ]

Implementation guide

This rule requires data from Elastic Defend with file and network event collection enabled.

Elastic Defend only generates file open events on macOS and Windows, and only for paths in its event-on-access file path list. That list is delivered through the Elastic Defend global configuration artifact and can be extended per policy with the advanced.events.event_on_access.file_paths advanced setting. Credential paths in this rule that are not yet present in the deployed global configuration only match after they are added to that setting. On Windows, repeated access events for the same path are deduplicated for one hour by default.

This rule is scoped to macOS and Windows because Elastic Defend does not generate file open events on Linux.

Known false positives

  • Developer or deployment scripts running on cloud-hosted macOS or Windows instances may read a package registry or source control token and then request instance role credentials from the same interpreter (for example a release script that publishes a package and uploads artifacts with the instance role). Validate the script path, parent process and whether the host is an approved build or release system.
  • AI coding assistants running on cloud instances may read their own credential file and then fall back to the instance metadata service for cloud provider authentication (for example Gemini CLI on Google Compute Engine). Confirm the process is the assistant itself and that the credential file belongs to that assistant.

Analyst notes

Investigating Package Runtime Secret File Access Followed by IMDS Request

This rule correlates two behaviors from the same package ecosystem runtime process (process.entity_id) within one minute: opening a local secret store that cloud SDKs never read, followed by a connection to a cloud instance metadata endpoint (169.254.169.254, the ECS and EKS Pod Identity credential endpoints, the AWS IPv6 IMDS endpoints, or the Alibaba Cloud metadata endpoint). Legitimate software rarely needs both an SSH key, registry token or AI assistant credential and cloud role credentials. Infostealers delivered through compromised packages routinely do both, typically from a Python or Node.js interpreter that imported the malicious package.

Possible investigation steps

  • Identify the process (process.executable, process.entity_id) and walk its ancestry to determine what launched it. Pay close attention to package managers, CI runners, notebooks, IDEs, AI coding agents, and interpreters executing from virtual environments or site-packages/node_modules.
  • Review all file events for the same process.entity_id to scope which secrets were accessed. Stealers usually enumerate many credential locations in a short burst.
  • Review all network events for the same process.entity_id for outbound connections to non-metadata destinations immediately after the IMDS request, which may indicate exfiltration of the harvested data.
  • Look for Python .pth files, sitecustomize.py/usercustomize.py, or recently installed or upgraded packages in the interpreter's environment that could have executed the code at import time.
  • Check cloud audit logs (CloudTrail, GCP Audit, Azure Activity) for use of the instance role credentials from IP addresses outside the instance's expected egress.

False positive analysis

  • Release or deployment scripts on cloud-hosted build agents may read a registry or source control token and then request instance role credentials from the same interpreter. Confirm the script is an approved part of a build or release pipeline on that host.
  • AI coding assistants on cloud instances may read their own credential file before falling back to the metadata server for cloud authentication. Confirm the process is the assistant that owns the credential file.

Response and remediation

  • Isolate the host and terminate the offending process tree.
  • Rotate every secret the process opened (SSH keys, npm/PyPI tokens, Git and GitHub CLI tokens, Vault and Terraform tokens, AI assistant OAuth tokens) and revoke active sessions for the instance role or workload identity.
  • Identify and remove the malicious package version from the environment, lock files and internal mirrors, and hunt for the same package version across other hosts and CI runners.
  • Enforce IMDSv2 with a hop limit of 1 where possible and scope instance roles to least privilege.
Raw source Package Runtime Secret File Access Followed by IMDS Request · Elastic TOML
Esc
Published by elastic/detection-rules ↗, licensed under Elastic License 2.0 ↗. Reproduced here unmodified.
[metadata]
creation_date = "2026/10/23"
integration = ["endpoint"]
maturity = "production"
updated_date = "2026/10/23"

[rule]
author = ["Elastic"]
description = """
Identifies a Python, Node.js, Bun, Deno, Ruby, PHP or Perl runtime, or a binary executing from a package install
location, that opens a developer secret (SSH keys, package registry tokens, source control credentials or AI coding
assistant credentials) and then connects to a cloud instance metadata service (IMDS) endpoint within one minute. This
sequence matches credential stealers delivered through compromised packages, such as the malicious LiteLLM PyPI
releases, while cloud provider credential files are excluded because legitimate cloud SDK credential chains read them
before falling back to IMDS.
"""
false_positives = [
    """
    Developer or deployment scripts running on cloud-hosted macOS or Windows instances may read a package registry or
    source control token and then request instance role credentials from the same interpreter (for example a release
    script that publishes a package and uploads artifacts with the instance role). Validate the script path, parent
    process and whether the host is an approved build or release system.
    """,
    """
    AI coding assistants running on cloud instances may read their own credential file and then fall back to the
    instance metadata service for cloud provider authentication (for example Gemini CLI on Google Compute Engine).
    Confirm the process is the assistant itself and that the credential file belongs to that assistant.
    """,
]
from = "now-9m"
index = ["logs-endpoint.events.file-*", "logs-endpoint.events.network-*"]
language = "eql"
license = "Elastic License v2"
name = "Package Runtime Secret File Access Followed by IMDS Request"
note = """## Triage and analysis

### Investigating Package Runtime Secret File Access Followed by IMDS Request

This rule correlates two behaviors from the same package ecosystem runtime process (`process.entity_id`) within one
minute: opening a local secret store that cloud SDKs never read, followed by a connection to a cloud instance metadata endpoint
(`169.254.169.254`, the ECS and EKS Pod Identity credential endpoints, the AWS IPv6 IMDS endpoints, or the Alibaba
Cloud metadata endpoint). Legitimate software rarely needs both an SSH key, registry token or AI assistant credential
and cloud role credentials. Infostealers delivered through compromised packages routinely do both, typically from a
Python or Node.js interpreter that imported the malicious package.

### Possible investigation steps

- Identify the process (`process.executable`, `process.entity_id`) and walk its ancestry to determine what launched it.
  Pay close attention to package managers, CI runners, notebooks, IDEs, AI coding agents, and interpreters executing
  from virtual environments or `site-packages`/`node_modules`.
- Review all file events for the same `process.entity_id` to scope which secrets were accessed. Stealers usually
  enumerate many credential locations in a short burst.
- Review all network events for the same `process.entity_id` for outbound connections to non-metadata destinations
  immediately after the IMDS request, which may indicate exfiltration of the harvested data.
- Look for Python `.pth` files, `sitecustomize.py`/`usercustomize.py`, or recently installed or upgraded packages in the
  interpreter's environment that could have executed the code at import time.
- Check cloud audit logs (CloudTrail, GCP Audit, Azure Activity) for use of the instance role credentials from IP
  addresses outside the instance's expected egress.

### False positive analysis

- Release or deployment scripts on cloud-hosted build agents may read a registry or source control token and then
  request instance role credentials from the same interpreter. Confirm the script is an approved part of a build or
  release pipeline on that host.
- AI coding assistants on cloud instances may read their own credential file before falling back to the metadata server
  for cloud authentication. Confirm the process is the assistant that owns the credential file.

### Response and remediation

- Isolate the host and terminate the offending process tree.
- Rotate every secret the process opened (SSH keys, npm/PyPI tokens, Git and GitHub CLI tokens, Vault and Terraform
  tokens, AI assistant OAuth tokens) and revoke active sessions for the instance role or workload identity.
- Identify and remove the malicious package version from the environment, lock files and internal mirrors, and hunt for
  the same package version across other hosts and CI runners.
- Enforce IMDSv2 with a hop limit of 1 where possible and scope instance roles to least privilege.
"""
risk_score = 73
rule_id = "2baf0a3c-50cd-43b1-9e9a-d7ff3153b36b"
setup = """## Setup

This rule requires data from Elastic Defend with file and network event collection enabled.

Elastic Defend only generates file `open` events on macOS and Windows, and only for paths in its event-on-access file
path list. That list is delivered through the Elastic Defend global configuration artifact and can be extended per
policy with the `advanced.events.event_on_access.file_paths` advanced setting. Credential paths in this rule that are
not yet present in the deployed global configuration only match after they are added to that setting. On Windows,
repeated access events for the same path are deduplicated for one hour by default.

This rule is scoped to macOS and Windows because Elastic Defend does not generate file `open` events on Linux.
"""
severity = "high"
tags = [
    "Domain: Endpoint",
    "Domain: Cloud",
    "OS: macOS",
    "OS: Windows",
    "Use Case: Threat Detection",
    "Tactic: Credential Access",
    "Tactic: Initial Access",
    "Data Source: Elastic Defend",
    "Resources: Investigation Guide",
    "Threat: Supply Chain",
    "Threat: IMDS Credential Theft",
    "Rule Type: Event Correlation (EQL)",
    "Platform: macOS",
    "Platform: Windows",
    "Domain: GenAI",
    "Mitre Atlas: AML.T0010",
    "Mitre Atlas: AML.T0010.001",
    "Mitre Atlas: AML.T0055",
    "Mitre Atlas: AML.T0083",
]
type = "eql"

query = '''
sequence by process.entity_id with maxspan=1m
  [file where event.action == "open" and host.os.type in ("macos", "windows") and
    file.path like~ (
      /* SSH private keys */
      "/Users/*/.ssh/id_*", "?:\\Users\\*\\.ssh\\id_*",

      /* Package registry, source control and secrets manager tokens */
      "/Users/*/.npmrc", "?:\\Users\\*\\.npmrc",
      "/Users/*/.pypirc", "?:\\Users\\*\\.pypirc",
      "/Users/*/.git-credentials", "?:\\Users\\*\\.git-credentials",
      "/Users/*/.config/git/credentials", "?:\\Users\\*\\.config\\git\\credentials",
      "/Users/*/.config/gh/hosts.yml", "?:\\Users\\*\\AppData\\Roaming\\GitHub CLI\\hosts.yml",
      "/Users/*/.vault-token", "?:\\Users\\*\\.vault-token",
      "/Users/*/.terraform.d/credentials.tfrc.json", "?:\\Users\\*\\AppData\\Roaming\\terraform.d\\credentials.tfrc.json",

      /* Additional package registry and AI/ML platform tokens */
      "/Users/*/.yarnrc.yml", "?:\\Users\\*\\.yarnrc.yml",
      "/Users/*/.gem/credentials", "?:\\Users\\*\\.gem\\credentials",
      "/Users/*/.cargo/credentials.toml", "?:\\Users\\*\\.cargo\\credentials.toml",
      "/Users/*/.composer/auth.json", "/Users/*/.config/composer/auth.json", "?:\\Users\\*\\AppData\\Roaming\\Composer\\auth.json",
      "/Users/*/.cache/huggingface/token", "/Users/*/.huggingface/token", "?:\\Users\\*\\.cache\\huggingface\\token", "?:\\Users\\*\\.huggingface\\token",
      "/Users/*/.config/github-copilot/apps.json", "/Users/*/.config/github-copilot/hosts.json",
      "?:\\Users\\*\\AppData\\Local\\github-copilot\\apps.json", "?:\\Users\\*\\AppData\\Local\\github-copilot\\hosts.json",
      "/Users/*/.ollama/id_ed25519", "?:\\Users\\*\\.ollama\\id_ed25519",

      /* AI coding assistant credentials */
      "/Users/*/.claude/.credentials.json", "?:\\Users\\*\\.claude\\.credentials.json",
      "/Users/*/.codex/auth.json", "?:\\Users\\*\\.codex\\auth.json",
      "/Users/*/.codex/.credentials.json", "?:\\Users\\*\\.codex\\.credentials.json",
      "/Users/*/.codex/secrets/*.age", "?:\\Users\\*\\.codex\\secrets\\*.age",
      "/Users/*/.gemini/oauth_creds.json", "?:\\Users\\*\\.gemini\\oauth_creds.json",
      "/Users/*/.gemini/gemini-credentials.json", "?:\\Users\\*\\.gemini\\gemini-credentials.json",
      "/Users/*/.gemini/mcp-oauth-tokens.json", "?:\\Users\\*\\.gemini\\mcp-oauth-tokens.json",
      "/Users/*/.gemini/a2a-oauth-tokens.json", "?:\\Users\\*\\.gemini\\a2a-oauth-tokens.json",
      "/Users/*/.qwen/oauth_creds.json", "?:\\Users\\*\\.qwen\\oauth_creds.json",
      "/Users/*/.qwen/mcp-oauth-tokens.json", "?:\\Users\\*\\.qwen\\mcp-oauth-tokens.json",
      "/Users/*/.aws/sso/cache/kiro-auth-token.json", "?:\\Users\\*\\.aws\\sso\\cache\\kiro-auth-token.json",
      "/Users/*/.local/share/opencode/auth.json", "?:\\Users\\*\\.local\\share\\opencode\\auth.json",
      "/Users/*/.local/share/opencode/mcp-auth.json", "?:\\Users\\*\\.local\\share\\opencode\\mcp-auth.json",
      "/Users/*/.local/share/amp/secrets.json", "?:\\Users\\*\\.local\\share\\amp\\secrets.json",
      "/Users/*/.augment/session.json", "?:\\Users\\*\\.augment\\session.json",
      "/Users/*/.kimi/credentials/kimi-code.json", "?:\\Users\\*\\.kimi\\credentials\\kimi-code.json",
      "/Users/*/.cursor/auth.json", "?:\\Users\\*\\.cursor\\auth.json",
      "/Users/*/.pi/agent/auth.json", "?:\\Users\\*\\.pi\\agent\\auth.json",
      "/Users/*/.config/goose/secrets.yaml", "?:\\Users\\*\\AppData\\Roaming\\Block\\goose\\config\\secrets.yaml",
      "/Users/*/.config/muse/auth.json", "?:\\Users\\*\\.config\\muse\\auth.json",
      "/Users/*/.local/share/devin/credentials.toml", "?:\\Users\\*\\AppData\\Roaming\\devin\\credentials.toml",
      "/Users/*/.openclaw/credentials/oauth.json", "?:\\Users\\*\\.openclaw\\credentials\\oauth.json",
      "/Users/*/.openclaw/agents/*/agent/auth-profiles.json", "?:\\Users\\*\\.openclaw\\agents\\*\\agent\\auth-profiles.json",
      "/Users/*/.openclaw/agents/*/agent/auth.json", "?:\\Users\\*\\.openclaw\\agents\\*\\agent\\auth.json"
    ) and

    /* SSH public keys are not secrets and are routinely read by SSH and cloud tooling */
    not file.path like~ "*.pub" and

    /* Package ecosystem runtimes or binaries executing from package install locations */
    (
      process.name like~ ("python*", "node", "node.exe", "nodejs", "bun", "bun.exe", "deno", "deno.exe", "ruby*", "php*", "perl*") or
      process.executable like~ ("*/site-packages/*", "*/dist-packages/*", "*/node_modules/*", "*\\site-packages\\*", "*\\node_modules\\*",
                                "*/.venv/*", "*/venv/*", "*\\.venv\\*", "*\\venv\\*")
    )
  ]
  [network where
    cidrmatch(destination.ip, "169.254.169.254/32", "169.254.170.2/32", "169.254.170.23/32", "100.100.100.200/32", "fd00:ec2::254/128", "fd00:ec2::23/128") and
    destination.port == 80 and event.action == "connection_attempted" and host.os.type in ("macos", "windows")
  ]
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1552"
name = "Unsecured Credentials"
reference = "https://attack.mitre.org/techniques/T1552/"
[[rule.threat.technique.subtechnique]]
id = "T1552.001"
name = "Credentials In Files"
reference = "https://attack.mitre.org/techniques/T1552/001/"

[[rule.threat.technique.subtechnique]]
id = "T1552.004"
name = "Private Keys"
reference = "https://attack.mitre.org/techniques/T1552/004/"

[[rule.threat.technique.subtechnique]]
id = "T1552.005"
name = "Cloud Instance Metadata API"
reference = "https://attack.mitre.org/techniques/T1552/005/"



[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1195"
name = "Supply Chain Compromise"
reference = "https://attack.mitre.org/techniques/T1195/"
[[rule.threat.technique.subtechnique]]
id = "T1195.001"
name = "Compromise Software Dependencies and Development Tools"
reference = "https://attack.mitre.org/techniques/T1195/001/"



[rule.threat.tactic]
id = "TA0001"
name = "Initial Access"
reference = "https://attack.mitre.org/tactics/TA0001/"
[[rule.threat_mappings]]
framework = "MITRE ATLAS"
version = "2026.08"
[[rule.threat_mappings.threat]]
framework = "MITRE ATLAS"
[[rule.threat_mappings.threat.technique]]
id = "AML.T0010"
name = "AI Supply Chain Compromise"
reference = "https://atlas.mitre.org/techniques/AML.T0010/"
[[rule.threat_mappings.threat.technique.subtechnique]]
id = "AML.T0010.001"
name = "AI Software"
reference = "https://atlas.mitre.org/techniques/AML.T0010.001/"



[rule.threat_mappings.threat.tactic]
id = "AML.TA0004"
name = "Initial Access"
reference = "https://atlas.mitre.org/tactics/AML.TA0004/"
[[rule.threat_mappings.threat]]
framework = "MITRE ATLAS"
[[rule.threat_mappings.threat.technique]]
id = "AML.T0055"
name = "Unsecured Credentials"
reference = "https://atlas.mitre.org/techniques/AML.T0055/"

[[rule.threat_mappings.threat.technique]]
id = "AML.T0083"
name = "Credentials from AI Agent Configuration"
reference = "https://atlas.mitre.org/techniques/AML.T0083/"


[rule.threat_mappings.threat.tactic]
id = "AML.TA0013"
name = "Credential Access"
reference = "https://atlas.mitre.org/tactics/AML.TA0013/"


Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.