
Auditing the Shai-Hulud Worm: From npm Script to Stolen AWS, GitHub, and K8s Credentials
What Shai-Hulud is, and why it matters to developers
Shai-Hulud is a developer-targeting worm that spreads through npm workflows and then hunts for high-value secrets on the host. The goal is not just one token. It is to turn a single compromised package or install step into access to GitHub, AWS, and Kubernetes environments that were never meant to be exposed to a random dependency.
That makes it different from a plain credential stealer. What makes it worth auditing is the way it targets the places developers already trust: package install hooks, local config files, CI runners, and shell environments. If your build process can run arbitrary package scripts, your dependency chain is also an execution chain.
The infection path through npm workflows
Why package scripts are such an effective entry point
npm scripts are powerful because they run with the same user privileges as the install process. That means a malicious preinstall, install, or postinstall script can read files, inspect environment variables, and spawn child processes without needing a browser exploit or a kernel bug.
A common audit mistake is to think “it’s just a package install.” It is not. It is code execution.
How install-time execution turns one compromised package into many hosts
The worm model works because developers reuse the same package versions across local machines, CI runners, and container builds. If one package gets poisoned, every fresh install becomes a possible infection point until the bad version is removed or pinned out.
This is the operational risk:
| Stage | What happens | Why it matters |
|---|---|---|
| Local dev | npm install runs scripts | Secrets in shell or dotfiles can be read |
| CI | Dependency install happens on shared runners | Tokens and cloud creds are often present |
| Build image | Package scripts run during image build | Compromise can be baked into artifacts |
What the worm looks for on disk and in process environments
npm and GitHub credentials
The obvious targets are npm tokens and GitHub credentials because they show up often in developer environments and CI. npm auth tokens can let an attacker publish or pull packages. GitHub tokens can expose private repos, release pipelines, and workflow secrets.
I would expect a worm in this class to check environment variables first, then local config files such as .npmrc, GitHub CLI state, and cached credentials under the user profile. If those values are available, there is no need to brute force anything.
AWS keys, session tokens, and CLI profiles
AWS access keys are especially dangerous because one leaked key rarely stays limited to one service. A session token, even if short-lived, can still be enough to enumerate roles, read secrets, or access storage buckets before it expires.
A good local audit should check for:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKEN~/.aws/credentials~/.aws/config
The detail that gets missed is that temporary credentials are not safe, just safer. If the worm runs during the token window, it still wins.
Kubernetes kubeconfig files and cluster access
Kubernetes access is attractive because kubeconfig often holds cluster endpoints, contexts, and bearer tokens. On a laptop or CI node, that can be enough to reach production-adjacent clusters.
If the infected process can read ~/.kube/config, the attacker may get more than cluster visibility. Depending on how the file is configured, it can expose a direct path to API access.
How exfiltration tends to happen in practice
Common outbound channels and why they are hard to spot
The stolen data usually leaves through normal outbound HTTP or HTTPS traffic. That is what makes this class of worm noisy in impact but quiet in network monitoring. It looks like a build tool calling home unless you are watching destination reputation, request timing, and unusual payload sizes.
A second issue is that exfiltration may be split into small chunks. That makes it easier to blend into ordinary telemetry or package metadata traffic.
The operational mistake that makes the leak persistent
The mistake I see most often is leaving long-lived credentials on developer machines and CI workers. If the same AWS key, GitHub token, or npm token sits in a shell profile for months, the worm only needs one successful run.
That turns a one-time infection into durable access.
Reproducing the risk safely in a local audit
Inspecting package scripts before install
Before running npm install on an untrusted package, inspect its manifest:
// quick-scan.js
const fs = require("fs");
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
console.log({
scripts: pkg.scripts,
dependencies: pkg.dependencies,
devDependencies: pkg.devDependencies,
});
What you are looking for is not only obvious shell commands. Watch for scripts that launch Node with bundled code, call out to curl or bash, or invoke a postinstall helper.
Tracing filesystem access and spawned child processes
In a test environment, trace what the installer reads and launches. On Linux, strace or auditd can show reads of ~/.npmrc, ~/.aws/credentials, or ~/.kube/config. On macOS, use filesystem and process monitoring tools that can show child process creation.
The question is simple: did a package install touch more than its own dependencies and build artifacts?
Watching network egress from a test runner
Put the install in a sandbox and watch outbound connections. A safe test runner should be able to tell you:
- which process opened the socket
- which host it contacted
- whether the payload was normal telemetry or credential-shaped data
You do not need to exfiltrate anything real to prove the risk. A dummy secret in a throwaway environment is enough to validate the path.
What actually reduces exposure
Locking down npm workflows and CI runners
Treat install scripts as code execution and reduce where they can run. Use pinned lockfiles, review dependency updates, and isolate CI runners so that package install jobs do not share broad secret access with deploy jobs.
Limiting token scope and shortening secret lifetime
Use the smallest scope that works and shorten lifetime wherever possible. If a token only needs read access for one job, do not hand it write permissions. If a session can expire in minutes instead of days, do that.
Hardening AWS and Kubernetes access paths
For AWS, prefer role-based access and session-scoped credentials over static keys. For Kubernetes, avoid leaving cluster-admin kubeconfigs on developer laptops and build hosts. The safest secret is the one the install step cannot read.
A practical checklist for developers and DevSecOps teams
- review
preinstall,install, andpostinstallscripts before introducing a package - keep npm, GitHub, AWS, and kube credentials out of shell profiles when possible
- separate build runners from deploy runners
- rotate long-lived tokens and remove unused ones
- monitor unexpected outbound requests during dependency installs
- sandbox package installation for untrusted or newly published dependencies
- verify that CI jobs only receive the secrets they actually need
Conclusion
Shai-Hulud matters because it uses ordinary developer workflows as the infection vector. That is the hard part to defend: the compromise does not need a fancy exploit if the build itself is allowed to execute attacker-controlled code.
If you want a real control, start with the install boundary. Treat every package script as potentially hostile, then make sure there is nothing valuable for it to steal.


