{
  "hunt": {
    "meta": {
      "tlp": "clear",
      "hunt": {
        "handoff": "keep-as-periodic-hunt",
        "trigger": "intel-report",
        "methodology": "model-assisted",
        "applicability": "campaign-specific",
        "justification": "The CHAINDROP worm targets the core assets of a modern engineering team: source code and cloud credentials. A negative result confirms that these high-value identities remain uncompromised by this specific campaign."
      },
      "name": "CHAINDROP: Host-Based Node.js Worm Execution and Harvesting",
      "type": "investigation",
      "labels": [
        "hunt",
        "attack.t1195.002",
        "attack.t1059.001",
        "attack.t1555"
      ],
      "series": {
        "slug": "shai-hulud-strikes-again-chaindrop-worm-hits-400-npm-packages",
        "index": 1,
        "title": "Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages",
        "total": 2
      },
      "related": [
        {
          "hunt": "chaindrop-network-propagation",
          "reason": "This hunt covers the local host artifacts; a subsequent hunt covers the network-based C2 resolution and exfiltration.",
          "relation": "follows"
        }
      ],
      "targets": {
        "hunter": {
          "name": "Hunt agent",
          "agent": true
        },
        "analyst": {
          "name": "Tier-2 analyst",
          "role": "analyst"
        },
        "endpoint": {
          "name": "Endpoint telemetry (hb_ surfaces)",
          "category": "endpoint",
          "telemetry": [
            "endpoint"
          ]
        }
      },
      "analysis": "A simple detection rule might flag the preinstall hook, but this hunt provides the context an analyst needs: it correlates the software inventory with the prevalence of the Bun runtime and the modification of IDE configuration files. The gated flow ensures that expensive behavioral analysis is only conducted when a viable lead is identified.",
      "coverage": [
        {
          "stage": "initial-access-npm-package",
          "steps": [
            "npm-inventory-lead"
          ],
          "status": "covered"
        },
        {
          "stage": "execution-via-dropper",
          "steps": [
            "dropper-execution-check",
            "rare-bun-execution"
          ],
          "status": "covered"
        },
        {
          "stage": "credential-harvesting",
          "steps": [
            "analyst-review"
          ],
          "status": "covered"
        },
        {
          "stage": "c2-discovery-and-exfiltration",
          "reason": "Belongs to another part of the 'Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages' series.",
          "status": "out_of_scope"
        },
        {
          "stage": "worm-propagation",
          "reason": "Belongs to another part of the 'Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages' series.",
          "status": "out_of_scope"
        }
      ],
      "scenario": {
        "stages": [
          {
            "name": "Trojanized npm Package Installation",
            "slug": "initial-access-npm-package",
            "tactic": "initial-access",
            "techniques": [
              "T1195.002"
            ],
            "observables": [
              "keyv",
              "flat-cache",
              "cacheable-request",
              "cacheable",
              "cache-manager",
              "preinstall hook in package.json",
              "setup.mjs"
            ]
          },
          {
            "name": "Multi-Path Dropper Execution",
            "slug": "execution-via-dropper",
            "tactic": "execution",
            "techniques": [
              "T1059.001"
            ],
            "observables": [
              "node setup.mjs",
              "bun v1.3.13",
              "Math_Symbol.js",
              "math_init.js",
              ".claude/settings.json SessionStart hook",
              ".vscode/tasks.json folderOpen task",
              "9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc",
              "fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb",
              "54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668"
            ]
          },
          {
            "name": "Developer and AI Credential Harvesting",
            "slug": "credential-harvesting",
            "tactic": "credential-access",
            "techniques": [
              "T1555"
            ],
            "observables": [
              "Anthropic",
              "Claude",
              "Codex",
              "Cursor",
              "OpenAI",
              "Gemini",
              "npm tokens",
              "GitHub PATs",
              "Kubernetes service account tokens",
              "HashiCorp Vault tokens"
            ]
          },
          {
            "name": "Dynamic C2 Discovery and Data Exfiltration",
            "slug": "c2-discovery-and-exfiltration",
            "tactic": "command-and-control",
            "techniques": [
              "T1041"
            ],
            "observables": [
              "0xE1f2395ee43e45A1556EC6438a88c31B83493103",
              "go.getblock.io",
              "eth.llamarpc.com",
              "awqhnjewqjkl.icu",
              "npm-cache.com",
              "thebeautifulmarchoftime",
              "Shai-Hulud: Here We Go Again"
            ]
          },
          {
            "name": "Self-Propagating Supply Chain Infection",
            "slug": "worm-propagation",
            "tactic": "persistence",
            "techniques": [
              "T1195"
            ],
            "observables": [
              "claude@users.noreply.github.com",
              "chore: update config",
              "IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients",
              "bypass_2fa"
            ]
          }
        ],
        "summary": "The Shai-Hulud campaign, also known as CHAINDROP, targets JavaScript developers via compromised npm maintainer accounts to deploy a self-propagating worm. The malware executes via npm hooks to steal cloud, AI, and developer credentials, then uses stolen npm tokens to automatically backdoor and republish all packages owned by the victim."
      },
      "severity": "high",
      "rationale": "The hunt focuses on developer endpoints and build servers where npm installs are frequent. The agent in 'evaluate-inventory' provides a list of hosts with confirmed software hits. The analyst must paste these hostnames into the 'scope_hosts' parameter for the subsequent process behavioral queries.",
      "guardrails": {
        "claims": "no_unsupported",
        "evidence": "citation_required",
        "telemetry": "untrusted",
        "missing_data": "not_benign"
      },
      "hypothesis": "An attacker has gained initial access through a backdoored npm package preinstall hook, which executes a dropper to install a rogue Bun runtime and harvest developer credentials from local IDE configurations.",
      "parameters": {
        "scope_hosts": {
          "type": "list[host]",
          "default": [],
          "description": "Host list returned from the scoping step to narrow behavior queries."
        },
        "lookback_days": {
          "type": "number",
          "default": "14",
          "description": "Days of history to examine for process activity."
        },
        "compromised_packages": {
          "from": {
            "ref": "Elastic Security Labs",
            "kind": "article",
            "observed": "2026-08-04"
          },
          "type": "list[string]",
          "default": [
            "keyv",
            "flat-cache",
            "cacheable-request",
            "cacheable",
            "cache-manager"
          ],
          "description": "Core npm packages identified as compromised in the CHAINDROP campaign."
        }
      },
      "provenance": {
        "authors": [
          {
            "org": "huntbase.io",
            "name": "Huntbase hunt generation"
          }
        ],
        "generated": {
          "by": "huntbase-hunt-generation",
          "from": "https://www.elastic.co/security-labs/threat-command/shai-hulud-chaindrop-npm-supply-chain",
          "gates": [
            "dry-run",
            "lint"
          ],
          "model": "hb_google/gemini-3-flash-preview"
        }
      },
      "references": [
        {
          "url": "https://www.elastic.co/security-labs/threat-command/shai-hulud-chaindrop-npm-supply-chain",
          "name": "Elastic Security Labs \u2014 Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages"
        }
      ],
      "blind_spots": [
        {
          "id": "transient-package-install",
          "risk": "hb_software_inventory is a point-in-time snapshot and will miss packages that were present only long enough for the preinstall hook to execute.",
          "stage": "initial-access-npm-package",
          "question": "whether a compromised package was installed and then removed within the inventory snapshot interval",
          "requires": "continuous package manager logging"
        },
        {
          "id": "short-retention-windows",
          "risk": "If the infection occurred weeks ago, the process activity logs may have rolled over, leaving only the persistent software inventory as evidence.",
          "stage": "execution-via-dropper",
          "question": "whether the dropper executed before the lookback window",
          "requires": "extended endpoint event retention"
        }
      ]
    },
    "name": "CHAINDROP: Host-Based Node.js Worm Execution and Harvesting",
    "description": "The CHAINDROP worm uses the npm supply chain by trojanizing popular packages with preinstall hooks. This hunt focuses on the endpoint footprint of the compromise: the presence of known-malicious npm packages, the execution of Node.js-based droppers, and the deployment of a rogue Bun runtime for credential harvesting. It employs a gated flow to first scope the environment for affected packages before fanning out into expensive behavioral analysis of process activity and prevalence-based detection of the Bun runtime."
  },
  "nodes": [
    {
      "id": "hypothesis",
      "type": "hypothesis",
      "label": "Hypothesis",
      "config": {
        "tags": [],
        "series": {
          "slug": "shai-hulud-strikes-again-chaindrop-worm-hits-400-npm-packages",
          "index": 1,
          "title": "Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages",
          "total": 2
        },
        "coverage": [
          {
            "stage": "initial-access-npm-package",
            "steps": [
              "npm-inventory-lead"
            ],
            "status": "covered"
          },
          {
            "stage": "execution-via-dropper",
            "steps": [
              "dropper-execution-check",
              "rare-bun-execution"
            ],
            "status": "covered"
          },
          {
            "stage": "credential-harvesting",
            "steps": [
              "analyst-review"
            ],
            "status": "covered"
          },
          {
            "stage": "c2-discovery-and-exfiltration",
            "reason": "Belongs to another part of the 'Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages' series.",
            "status": "out_of_scope"
          },
          {
            "stage": "worm-propagation",
            "reason": "Belongs to another part of the 'Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages' series.",
            "status": "out_of_scope"
          }
        ],
        "rationale": "An attacker has gained initial access through a backdoored npm package preinstall hook, which executes a dropper to install a rogue Bun runtime and harvest developer credentials from local IDE configurations.",
        "blind_spots": [
          {
            "id": "transient-package-install",
            "risk": "hb_software_inventory is a point-in-time snapshot and will miss packages that were present only long enough for the preinstall hook to execute.",
            "stage": "initial-access-npm-package",
            "question": "whether a compromised package was installed and then removed within the inventory snapshot interval",
            "requires": "continuous package manager logging"
          },
          {
            "id": "short-retention-windows",
            "risk": "If the infection occurred weeks ago, the process activity logs may have rolled over, leaving only the persistent software inventory as evidence.",
            "stage": "execution-via-dropper",
            "question": "whether the dropper executed before the lookback window",
            "requires": "extended endpoint event retention"
          }
        ],
        "scoping_notes": "The hunt focuses on developer endpoints and build servers where npm installs are frequent. The agent in 'evaluate-inventory' provides a list of hosts with confirmed software hits. The analyst must paste these hostnames into the 'scope_hosts' parameter for the subsequent process behavioral queries.",
        "beyond_detection": "A simple detection rule might flag the preinstall hook, but this hunt provides the context an analyst needs: it correlates the software inventory with the prevalence of the Bun runtime and the modification of IDE configuration files. The gated flow ensures that expensive behavioral analysis is only conducted when a viable lead is identified."
      }
    },
    {
      "id": "npm-inventory-lead",
      "type": "query",
      "label": "Find compromised npm packages in inventory",
      "config": {
        "dsl": "sqlite",
        "role": "scoping",
        "source": "endpoint",
        "content": "SELECT device_hostname, package_name, package_version, install_path FROM hb_software_inventory WHERE package_type = 'npm' AND instr(',' || '{{compromised_packages}}' || ',', ',' || LOWER(package_name) || ',') > 0",
        "surface": "hb_software_inventory",
        "description": "Identify hosts that have installed the primary compromised packages named in the research to narrow the scope.",
        "expected_signal": "Hosts with known-compromised npm packages installed. Silence indicates these specific packages are not in the current inventory snapshot."
      },
      "parents": [
        {
          "id": "hypothesis"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "scoping",
        "label": "Find compromised npm packages in inventory",
        "reads": [
          "device_hostname",
          "package_name",
          "package_version",
          "install_path"
        ],
        "source": "hb_software_inventory",
        "target": "endpoint",
        "content": "SELECT device_hostname, package_name, package_version, install_path FROM hb_software_inventory WHERE package_type = 'npm' AND instr(',' || '{{compromised_packages}}' || ',', ',' || LOWER(package_name) || ',') > 0",
        "silence": "not_evidence_of_absence",
        "expected": "Hosts with known-compromised npm packages installed. Silence indicates these specific packages are not in the current inventory snapshot.",
        "verified": "dry-run",
        "verified_at": "2026-09-20"
      }
    },
    {
      "id": "evaluate-inventory",
      "type": "analytic",
      "label": "Evaluate software inventory lead",
      "config": {
        "cite": "required",
        "tools": [
          "endpoint"
        ],
        "context": [
          "npm-inventory-lead"
        ],
        "objective": "Determine if the identified npm packages on any host match the threat profile described in the research and warrant a full behavioral investigation.",
        "description": "Determine if any host requires further inspection based on the specific npm packages found.",
        "max_iterations": 3,
        "expected_signal": "A per-host verdict recommending either behavioral inspection or closure.",
        "success_criteria": "A verdict for each host naming the compromised package and recommending next steps."
      },
      "parents": [
        {
          "id": "npm-inventory-lead"
        }
      ]
    },
    {
      "id": "gate-on-lead",
      "type": "checkpoint",
      "label": "Gate on inventory lead",
      "config": {
        "fuzzy": true,
        "judge": "hunter",
        "question": "the evaluate-inventory verdict identifies at least one host with a compromised npm package",
        "condition": "the evaluate-inventory verdict identifies at least one host with a compromised npm package",
        "blind_spot": "transient-package-install",
        "confidence": "high",
        "description": "Avoid expensive behavioral queries unless a compromised package is confirmed.",
        "checkpoint_type": "mandatory"
      },
      "parents": [
        {
          "id": "evaluate-inventory"
        }
      ]
    },
    {
      "id": "dropper-execution-check",
      "type": "query",
      "label": "Detect dropper and payload execution",
      "config": {
        "dsl": "sqlite",
        "role": "detection-candidate",
        "source": "endpoint",
        "content": "SELECT device_hostname, process_name, process_cmd_line, user_name, time FROM hb_process_activity WHERE ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND (LOWER(process_cmd_line) LIKE '%setup.mjs%' OR LOWER(process_cmd_line) LIKE '%math_symbol.js%' OR LOWER(process_cmd_line) LIKE '%math_init.js%') AND time >= datetime('now', '-{{lookback_days}} days')",
        "surface": "hb_process_activity",
        "description": "Find the specific process execution strings associated with the CHAINDROP dropper and its obfuscated payload.",
        "expected_signal": "Process events showing node.exe or node executing the dropper files. Silence suggests the execution phase did not occur in the window."
      },
      "parents": [
        {
          "id": "gate-on-lead",
          "branch": "on_supports"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "detection-candidate",
        "label": "Detect dropper and payload execution",
        "reads": [
          "device_hostname",
          "process_name",
          "process_cmd_line",
          "user_name",
          "time"
        ],
        "source": "hb_process_activity",
        "target": "endpoint",
        "content": "SELECT device_hostname, process_name, process_cmd_line, user_name, time FROM hb_process_activity WHERE ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND (LOWER(process_cmd_line) LIKE '%setup.mjs%' OR LOWER(process_cmd_line) LIKE '%math_symbol.js%' OR LOWER(process_cmd_line) LIKE '%math_init.js%') AND time >= datetime('now', '-{{lookback_days}} days')",
        "silence": "not_evidence_of_absence",
        "expected": "Process events showing node.exe or node executing the dropper files. Silence suggests the execution phase did not occur in the window.",
        "verified": "dry-run",
        "verified_at": "2026-09-20"
      }
    },
    {
      "id": "rare-bun-execution",
      "type": "query",
      "label": "Stack-count rare Bun runtime usage",
      "config": {
        "dsl": "sqlite",
        "role": "baseline",
        "source": "endpoint",
        "content": "SELECT LOWER(process_path) AS path, process_cmd_line, user_name, COUNT(DISTINCT device_hostname) AS hosts, MIN(time) AS first_seen FROM hb_process_activity WHERE (LOWER(process_name) IN ('bun', 'bun.exe') OR LOWER(process_path) LIKE '%/bun%') AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY 1, 2, 3 HAVING hosts <= 5 ORDER BY hosts ASC",
        "surface": "hb_process_activity",
        "description": "Identify hosts where the Bun runtime is executing from unusual or temporary paths, which is characteristic of this worm.",
        "expected_signal": "A list of hosts where Bun has run recently from a path seen on 5 or fewer hosts. Silence means Bun usage is either absent or fleet-wide."
      },
      "parents": [
        {
          "id": "gate-on-lead",
          "branch": "on_supports"
        }
      ],
      "primitive_config": {
        "dsl": "sqlite",
        "role": "baseline",
        "label": "Stack-count rare Bun runtime usage",
        "reads": [
          "process_path",
          "device_hostname",
          "process_name",
          "process_cmd_line",
          "user_name",
          "time"
        ],
        "source": "hb_process_activity",
        "target": "endpoint",
        "content": "SELECT LOWER(process_path) AS path, process_cmd_line, user_name, COUNT(DISTINCT device_hostname) AS hosts, MIN(time) AS first_seen FROM hb_process_activity WHERE (LOWER(process_name) IN ('bun', 'bun.exe') OR LOWER(process_path) LIKE '%/bun%') AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY 1, 2, 3 HAVING hosts <= 5 ORDER BY hosts ASC",
        "silence": "not_evidence_of_absence",
        "baseline": {
          "window": "{{lookback_days}}d",
          "compare": "first_seen"
        },
        "expected": "A list of hosts where Bun has run recently from a path seen on 5 or fewer hosts. Silence means Bun usage is either absent or fleet-wide.",
        "verified": "dry-run",
        "prevalence": {
          "by": "device_hostname",
          "key": [
            "path",
            "process_cmd_line",
            "user_name"
          ],
          "rare_below": 5
        },
        "verified_at": "2026-09-20"
      }
    },
    {
      "id": "final-triage",
      "type": "analytic",
      "label": "Final triage of CHAINDROP activity",
      "config": {
        "cite": "required",
        "tools": [
          "endpoint"
        ],
        "context": [
          "evaluate-inventory",
          "dropper-execution-check",
          "rare-bun-execution"
        ],
        "objective": "Determine if the combined signals confirm a successful supply-chain infection and subsequent malicious execution on any host.",
        "description": "Synthesize inventory hits, dropper execution, and rare runtime usage to confirm infection.",
        "max_iterations": 6,
        "expected_signal": "A detailed verdict citing specific rows across the queries.",
        "success_criteria": "A verdict of infected | suspicious | benign for each host, citing the package, the dropper process, and the Bun execution path."
      },
      "parents": [
        {
          "id": "dropper-execution-check",
          "kind": "merge"
        },
        {
          "id": "rare-bun-execution",
          "kind": "merge"
        }
      ]
    },
    {
      "id": "route-infection",
      "type": "checkpoint",
      "label": "Route on infection verdict",
      "config": {
        "fuzzy": true,
        "judge": "hunter",
        "question": "the final-triage verdict is infected for at least one host",
        "condition": "the final-triage verdict is infected for at least one host",
        "blind_spot": "short-retention-windows",
        "confidence": "high",
        "description": "Initiate immediate response for confirmed infections.",
        "checkpoint_type": "mandatory"
      },
      "parents": [
        {
          "id": "final-triage"
        }
      ]
    },
    {
      "id": "isolate-host",
      "type": "action",
      "label": "Isolate infected host",
      "config": {
        "target": "endpoint",
        "description": "Stop further credential harvesting and prevent the worm from propagating.",
        "instructions": "Isolate the host immediately. Notify the identity team to revoke all npm and GitHub tokens associated with the user on this machine.",
        "action_approval": "required"
      },
      "parents": [
        {
          "id": "route-infection",
          "branch": "on_supports"
        }
      ]
    },
    {
      "id": "analyst-review",
      "type": "task",
      "label": "Review behavioral activity",
      "config": {
        "assignee": "analyst",
        "description": "Verify the extent of the harvesting and check for IDE hook persistence.",
        "instructions": "Review hb_file_activity for modifications to .vscode/tasks.json or .claude/settings.json. Examine hb_script_activity for the full text of the obfuscated payload execution blocks."
      },
      "parents": [
        {
          "id": "gate-on-lead",
          "branch": "default"
        },
        {
          "id": "gate-on-lead",
          "branch": "on_unavailable"
        },
        {
          "id": "route-infection",
          "branch": "default"
        },
        {
          "id": "route-infection",
          "branch": "on_unavailable"
        },
        {
          "id": "route-infection",
          "branch": "on_refutes"
        },
        {
          "id": "isolate-host"
        }
      ]
    },
    {
      "id": "close-out",
      "type": "task",
      "label": "Close out hunt",
      "config": {
        "assignee": "analyst",
        "description": "Document findings for the audit record.",
        "instructions": "The hunt found no primary compromised packages or dropper execution paths during the lookback period."
      },
      "parents": [
        {
          "id": "gate-on-lead",
          "branch": "on_refutes"
        }
      ]
    }
  ]
}