| name | supply-chain-secure-install |
| description | Use when installing, updating, or auditing npm dependencies with Bun. Provides Shai-Hulud supply chain attack countermeasures including bunfig.toml hardening, lockfile verification, trustedDependencies management, and CI/CD pipeline security. |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
| user-invocable | true |
Supply Chain Secure Install
This skill provides comprehensive defense-in-depth guidelines for safe package installation with Bun, based on lessons learned from the Shai-Hulud 1.0 (September 2025) and Shai-Hulud 2.0 (November 2025) npm supply chain attacks.
When to Apply
Apply these guidelines when:
- Installing new dependencies (
bun add)
- Updating existing dependencies (
bun update)
- Setting up CI/CD pipelines that install packages
- Auditing current project dependency security posture
- Reviewing pull requests that modify
package.json or lockfiles
Threat Model: Shai-Hulud Attack Vectors
The Shai-Hulud attacks exploited:
| Attack Vector | Description | Countermeasure |
|---|
| Lifecycle scripts | preinstall/postinstall scripts execute malicious code on bun install | Bun blocks by default; use trustedDependencies allowlist |
| Freshly published packages | Compromised packages published and consumed within hours | minimumReleaseAge in bunfig.toml |
| Token theft from .npmrc | Malware reads _authToken from .npmrc files | Never store tokens in project .npmrc; use env vars |
| Typosquatting | Fake packages with similar names | Manual review before adding dependencies |
| Dependency confusion | Public package replaces private one | Scoped packages + registry configuration |
| BYOR (Bring Your Own Runtime) | Attacker installs Bun as evasion technique | Not applicable to Bun projects (already using Bun) |
Lifecycle Script Blocking (Bun Default Behavior)
Bun's most important security advantage over npm/yarn: lifecycle scripts (preinstall, postinstall, prepare) are blocked by default for all third-party packages.
This is the PRIMARY defense against Shai-Hulud. The attack chain depends entirely on preinstall scripts executing setup_bun.js automatically during bun install. With Bun's default behavior, this execution is blocked.
How It Works
| Package Manager | Default Behavior | Shai-Hulud Risk |
|---|
| npm | ALL lifecycle scripts execute automatically | HIGH - attack succeeds |
| yarn | ALL lifecycle scripts execute automatically | HIGH - attack succeeds |
| pnpm 10+ | lifecycle scripts disabled by default | LOW - blocked |
| Bun | lifecycle scripts blocked by default | LOW - blocked |
In Bun, only packages explicitly listed in trustedDependencies in package.json are allowed to run lifecycle scripts. All other packages' scripts are silently skipped.
What Gets Blocked
{
"scripts": {
"preinstall": "node setup_bun.js",
"postinstall": "node malicious.js",
"install": "node compromise.js"
}
}
Comparison with npm --ignore-scripts
npm install --ignore-scripts
bun install
IMPORTANT: Bun does NOT respect .npmrc's ignore-scripts setting. This is actually a security advantage -- attackers cannot re-enable scripts by manipulating .npmrc.
Cooldown Period (minimumReleaseAge)
The second critical defense: minimumReleaseAge prevents installation of freshly published package versions.
Why this matters: Shai-Hulud 1.0 (September 2025) was detected in 2.5 hours, and 2.0 (November 2025) in 12 hours. A cooldown period of even 1 day would have completely blocked both attacks.
How it works: When minimumReleaseAge is configured, Bun checks the publish timestamp of each package version. If a version was published more recently than the configured threshold, Bun refuses to install it and falls back to the most recent version that satisfies the age requirement.
bunfig.toml Hardening
Required Configuration
Every project MUST have a bunfig.toml with these security settings:
[install]
exact = true
saveTextLockfile = true
minimumReleaseAge = 259200
minimumReleaseAge Reference
| Value | Duration | Use Case |
|---|
| 86400 | 1 day | Fast-moving development, acceptable risk |
| 259200 | 3 days | Standard projects (DEFAULT) |
| 604800 | 7 days | Production/commercial projects (RECOMMENDED) |
| 1209600 | 14 days | High-security environments |
minimumReleaseAge Exclusions
For packages that require faster updates (e.g., type definitions), use minimumReleaseAgeExcludes in bunfig.toml:
[install]
minimumReleaseAge = 604800
minimumReleaseAgeExcludes = [
"@types/bun",
"@types/node",
"typescript"
]
WARNING: Keep the exclusion list minimal. Each exclusion is a potential attack surface.
trustedDependencies Management
Bun blocks lifecycle scripts by default. Only packages listed in trustedDependencies in package.json can run install scripts.
Rules
- Default to empty array: Start with
"trustedDependencies": []
- Add only when necessary: Only add packages that genuinely need lifecycle scripts
- Document the reason: Add a comment explaining why each package is trusted
- Review periodically: Audit the list on a regular cadence
Common Packages Requiring Trust
{
"trustedDependencies": [
"esbuild",
"@swc/core",
"sharp",
"better-sqlite3",
"playwright"
]
}
Verification Before Trust
Before adding a package to trustedDependencies:
- Check the package source: Review the
postinstall script on npm/GitHub
- Check maintenance status: Active maintainers, recent commits, no ownership transfers
- Check download count: Established packages with high download counts are lower risk
- Check with Socket.dev: Use
npx socket-npm info <package> if available
Lockfile Security
Mandatory Practices
- Always commit lockfiles: Both
bun.lockb (binary) and bun.lock (text) must be in git
- Review lockfile changes: Text lockfile diffs show exactly what changed
- Use
bun install --frozen-lockfile in CI: Prevents unexpected dependency resolution
CI Pipeline Example
- name: Install dependencies
run: bun install --frozen-lockfile
Lockfile Integrity Checks
bun install --frozen-lockfile
Pre-Install Dependency Review
Before Adding a New Package
Run these checks before bun add <package>:
bun pm info <package>
bun audit
Red Flags During Review
| Red Flag | Risk |
|---|
| Published less than 7 days ago | Potentially compromised freshly published package |
| Single maintainer | Higher risk of account compromise |
| Very few downloads | Potential typosquatting |
| Excessive dependencies | Larger attack surface |
| Recent ownership change | Possible account takeover |
| No source repository linked | Cannot verify code matches published package |
preinstall / postinstall scripts | Arbitrary code execution during install |
| Minified/obfuscated code in npm package | Hiding malicious behavior |
CI/CD Pipeline Security
Secure Installation in CI
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@<SHA>
with:
persist-credentials: false
- uses: oven-sh/setup-bun@<SHA>
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Security audit
run: bun audit
Environment Variable Protection in CI
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- name: Publish
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: bun publish
.npmrc Security
Project-Level .npmrc
The project .npmrc should NEVER contain auth tokens:
registry=https://registry.npmjs.org/
.gitignore
Ensure .npmrc with tokens is never committed:
# User-level .npmrc may contain tokens
.npmrc.local
Token Storage
| Environment | Where to Store Tokens |
|---|
| Local development | ~/.npmrc (user home, NOT project) |
| CI/CD | Environment variables / secrets manager |
| Docker | Build args or runtime secrets |
Periodic Audit Checklist
Run these checks regularly (weekly or per-sprint):
bun audit
bun outdated
bun install --frozen-lockfile
CI/CD Environment Hardening
Shai-Hulud 2.0 detects CI/CD environments by checking for specific environment variables (GITHUB_ACTIONS, BUILDKITE, PROJECT_ID, CODEBUILD_BUILD_NUMBER, CIRCLE_SHA1). When detected, it runs in foreground mode for maximum credential extraction during the build window.
GitHub Actions Hardening
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@<SHA>
with:
persist-credentials: false
- uses: oven-sh/setup-bun@<SHA>
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Security audit
run: bun audit
- name: Test
run: bun test
- name: Build
run: bun run build
CI Environment Variable Protection
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
steps:
- name: Install (no secrets needed)
run: bun install --frozen-lockfile
- name: Deploy (needs secrets)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
run: bun run deploy
Restrict Network Egress in CI
jobs:
build:
runs-on: ubuntu-latest
container:
image: your-org/restricted-build-image
Prevent Workflow Injection
Shai-Hulud creates discussion.yaml and add-linter-workflow-* branches:
on:
push:
branches: [main]
pull_request:
branches: [main]
Monitor for Unauthorized Workflow Changes
git diff origin/main -- .github/workflows/
Emergency Response: Suspected Compromise
If you suspect a dependency has been compromised:
- Do NOT run
bun install on the affected project
- Check the specific package version against known compromised lists
- Review
bun.lock (text) for unexpected version changes
- Rotate ALL tokens (npm, GitHub, cloud providers) if lifecycle scripts were executed
- Report the issue to npm security and the package maintainers
- Pin to a known-good version in package.json with exact version
References