.agents/skills/supply-chain-secure-install/SKILL.md
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.
npx skillsauth add tacogips/claude-code-agent supply-chain-secure-installInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Apply these guidelines when:
bun add)bun update)package.json or lockfilesThe 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) |
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.
| 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.
// In a compromised package's package.json:
{
"scripts": {
"preinstall": "node setup_bun.js", // BLOCKED by Bun
"postinstall": "node malicious.js", // BLOCKED by Bun
"install": "node compromise.js" // BLOCKED by Bun
}
}
# npm requires explicit flag (easy to forget)
npm install --ignore-scripts
# pnpm 10+ disables by default, but respects .npmrc
# WARNING: Shai-Hulud could manipulate .npmrc to re-enable scripts
# Bun: blocked by default, CANNOT be re-enabled via .npmrc
# Only trustedDependencies allowlist enables scripts
bun install # Scripts already blocked, no flag needed
IMPORTANT: Bun does NOT respect .npmrc's ignore-scripts setting. This is actually a security advantage -- attackers cannot re-enable scripts by manipulating .npmrc.
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.
Every project MUST have a bunfig.toml with these security settings:
[install]
# Pin exact versions - no semver ranges
exact = true
# Generate text lockfile for reviewable git diffs
saveTextLockfile = true
# CRITICAL: Minimum age before a package version can be installed
# 259200 = 3 days (Shai-Hulud 1.0 was detected in 2.5 hours)
# 604800 = 7 days (recommended for production projects)
minimumReleaseAge = 259200
# NOTE: Bun blocks lifecycle scripts by default (secure by design)
# Use "trustedDependencies" in package.json to allowlist specific packages
| 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 |
For packages that require faster updates (e.g., type definitions), use minimumReleaseAgeExcludes in bunfig.toml:
[install]
minimumReleaseAge = 604800
# Packages excluded from release age requirement
minimumReleaseAgeExcludes = [
"@types/bun",
"@types/node",
"typescript"
]
WARNING: Keep the exclusion list minimal. Each exclusion is a potential attack surface.
Bun blocks lifecycle scripts by default. Only packages listed in trustedDependencies in package.json can run install scripts.
"trustedDependencies": []{
"trustedDependencies": [
"esbuild",
"@swc/core",
"sharp",
"better-sqlite3",
"playwright"
]
}
Before adding a package to trustedDependencies:
postinstall script on npm/GitHubnpx socket-npm info <package> if availablebun.lockb (binary) and bun.lock (text) must be in gitbun install --frozen-lockfile in CI: Prevents unexpected dependency resolution- name: Install dependencies
run: bun install --frozen-lockfile
# Verify lockfile is up-to-date without modifying it
bun install --frozen-lockfile
# If this fails, the lockfile is out of sync with package.json
# Developer must run `bun install` locally and commit the updated lockfile
Run these checks before bun add <package>:
# 1. Check package info on npm
bun pm info <package>
# 2. Check for known vulnerabilities
bun audit
# 3. Review the package on npm website
# Check: maintainers, last publish date, weekly downloads, dependencies count
# 4. Check for typosquatting
# Verify the exact package name matches the official one
# Common tricks: lodash vs 1odash, express vs expresss
| 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 |
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read # Minimal permissions
steps:
- uses: actions/checkout@<SHA>
with:
persist-credentials: false
- uses: oven-sh/setup-bun@<SHA>
with:
bun-version: latest
# CRITICAL: frozen lockfile prevents supply chain manipulation
- name: Install dependencies
run: bun install --frozen-lockfile
# Run audit after install
- name: Security audit
run: bun audit
# NEVER expose npm tokens as environment variables accessible to all steps
# Use step-level env only where needed
# BAD
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# GOOD - only expose to the step that needs it
steps:
- name: Publish
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: bun publish
The project .npmrc should NEVER contain auth tokens:
# .npmrc - safe settings only
registry=https://registry.npmjs.org/
# Do NOT put _authToken here
Ensure .npmrc with tokens is never committed:
# User-level .npmrc may contain tokens
.npmrc.local
| Environment | Where to Store Tokens |
|------------|----------------------|
| Local development | ~/.npmrc (user home, NOT project) |
| CI/CD | Environment variables / secrets manager |
| Docker | Build args or runtime secrets |
Run these checks regularly (weekly or per-sprint):
# 1. Check for known vulnerabilities
bun audit
# 2. Check for outdated packages
bun outdated
# 3. Verify lockfile integrity
bun install --frozen-lockfile
# 4. Review trustedDependencies list
# Ensure each entry is still necessary
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.
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15 # Prevent long-running malicious processes
permissions:
contents: read # MINIMUM permissions
# Do NOT add: actions: write, administration, workflows
steps:
- uses: actions/checkout@<SHA> # Always pin to SHA
with:
persist-credentials: false # Prevent token leakage
- uses: oven-sh/setup-bun@<SHA> # Pin to SHA
with:
bun-version: latest
# CRITICAL: frozen lockfile in CI
- name: Install dependencies
run: bun install --frozen-lockfile
# Audit after install
- name: Security audit
run: bun audit
# Tests and build
- name: Test
run: bun test
- name: Build
run: bun run build
# BAD: Global env exposes tokens to ALL steps (including compromised dependencies)
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
# GOOD: Step-level env exposes tokens ONLY to the step that needs them
steps:
- name: Install (no secrets needed)
run: bun install --frozen-lockfile
# NO env: block - compromised dependency cannot access secrets
- name: Deploy (needs secrets)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
run: bun run deploy
# Use GitHub Actions network restrictions (if available)
# Or use a custom container with restricted networking
jobs:
build:
runs-on: ubuntu-latest
container:
image: your-org/restricted-build-image
# Container with iptables rules blocking non-essential outbound
Shai-Hulud creates discussion.yaml and add-linter-workflow-* branches:
# Branch protection rules (via GitHub settings):
# - Require PR reviews before merging
# - Require status checks
# - Restrict push to main branch
# - Require signed commits
# In workflow: only trigger on trusted events
on:
push:
branches: [main] # NOT on all branches
pull_request:
branches: [main] # NOT pull_request_target
# In CI: verify no unexpected workflows were added
git diff origin/main -- .github/workflows/
# Fail the build if unexpected workflow files appear
If you suspect a dependency has been compromised:
bun install on the affected projectbun.lock (text) for unexpected version changesdevelopment
Use when writing, reviewing, or refactoring TypeScript code. Provides type safety patterns, error handling, project layout, and async programming guidelines.
development
Use when refactoring tests for better maintainability. Provides guidelines for removing duplicates, DRYing fixtures/assertions, restructuring test organization, renaming, and splitting oversized files.
testing
Use when creating test plans from implementation and design documents. Provides test plan structure, test case tracking, and coverage guidelines.
testing
Use this skill when creating or modifying GitHub Actions workflow files (.github/workflows/*.yml). Ensures all actions are pinned by commit SHA, permissions are minimized, script injection is prevented, and other supply chain security best practices are applied.