| name | supply-chain-secure-publish |
| description | Use when creating, publishing, or maintaining npm packages with Bun. Provides Shai-Hulud supply chain attack countermeasures including npm token management, 2FA enforcement, provenance signing, trusted publishing via GitHub Actions, and pre-publish security checklists. |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
| user-invocable | true |
Supply Chain Secure Publish
This skill provides comprehensive guidelines for securely creating and publishing npm packages with Bun, based on lessons learned from the Shai-Hulud supply chain attacks where compromised maintainer accounts were used to inject malware into trusted packages.
When to Apply
Apply these guidelines when:
- Creating a new npm package
- Publishing package updates to npm registry
- Setting up CI/CD publishing pipelines
- Managing npm access tokens and permissions
- Auditing existing package publishing workflows
Threat Model: How Packages Get Compromised
In the Shai-Hulud attacks, the publish-side attack chain was:
Phishing email -> Maintainer npm token stolen -> Malicious version published
-> preinstall script injected -> Worm propagates to all maintainer's packages
Key attack details:
- Attackers obtained npm tokens via phishing and credential theft
- Used stolen tokens to
npm publish backdoored versions (patch bump)
- Injected
preinstall scripts pointing to setup_bun.js loader
- Automated: processed up to 100 packages per compromised account in parallel
- Patch version bumps appeared as routine bug fixes
npm Account Security
Mandatory: Enable 2FA
npm profile enable-2fa auth-and-writes
| 2FA Mode | Protection Level | Recommendation |
|---|
auth-only | Protects login only | INSUFFICIENT |
auth-and-writes | Protects login AND publish | REQUIRED |
Token Management
Token Types and When to Use
| Token Type | Use Case | Shai-Hulud Risk |
|---|
| Classic token (deprecated) | Legacy systems | HIGH - long-lived, broad scope |
| Granular access token | CI/CD publishing | LOWER - scoped, time-limited |
| OIDC / Trusted Publishing | GitHub Actions | LOWEST - no stored secrets |
Granular Token Best Practices
When creating npm tokens:
- Use granular access tokens (not classic tokens)
- Scope to specific packages - never use tokens with access to all packages
- Set expiration - 30 days maximum for CI tokens
- Use read-only tokens where publish is not needed
- Set CIDR allowlist - restrict token usage to known CI IP ranges
npm token create --read-only
Token Storage
| Location | Safe? | Notes |
|---|
.npmrc in project directory | NO | Committed to git, stolen by malware |
~/.npmrc in home directory | RISKY | Shai-Hulud specifically targets this file |
| CI/CD secrets manager | YES | Encrypted, scoped, auditable |
| Environment variable in CI | YES | Ephemeral, per-job |
| GitHub Actions OIDC | BEST | No stored secrets at all |
.npmrc Token Hygiene
grep -r "authToken" .npmrc 2>/dev/null && echo "WARNING: Token found in project .npmrc!"
grep "authToken" ~/.npmrc 2>/dev/null && echo "Token found in ~/.npmrc (expected for local dev)"
CRITICAL: The Shai-Hulud worm specifically searches for .npmrc files in:
- Current working directory
- User home directory (
~/)
Both locations are targeted for _authToken extraction.
Trusted Publishing (OIDC)
GitHub Actions Trusted Publishing (Recommended)
Trusted Publishing eliminates stored npm tokens entirely. GitHub Actions authenticates directly with npm via OIDC.
Setup Steps
-
Link your npm package to a GitHub repository on npmjs.com:
- Go to package settings -> "Publishing access"
- Add GitHub Actions as a trusted publisher
- Specify: repository owner, repository name, workflow filename, optional environment
-
Create the publish workflow:
name: Publish Package
on:
release:
types: [created]
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
environment: npm-publish
timeout-minutes: 10
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: Run tests
run: bun test
- name: Type check
run: bun run typecheck
- name: Build
run: bun run build
- name: Publish to npm
run: bunx npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ""
Benefits of Trusted Publishing
| Aspect | Token-based | Trusted Publishing |
|---|
| Token theft risk | HIGH | NONE (no token) |
| Token rotation needed | YES | NO |
| Audit trail | Token ID only | Full workflow provenance |
| Scope limitation | Manual | Automatic (repo + workflow) |
| Setup complexity | Low | Medium (one-time) |
Package Provenance
npm Provenance
Publishing with --provenance creates a verifiable link between the published package and its source code:
bunx npm publish --provenance --access public
Provenance proves:
- Which source repository the package was built from
- Which commit SHA was used
- Which CI workflow built and published it
- That no human manually modified the package
Verifying Provenance
npm audit signatures
Pre-Publish Security Checklist
Before every publish, verify:
1. Package Contents Audit
bun pm pack --dry-run
bunx npm pack --dry-run
Verify:
2. files / .npmignore Configuration
Use files in package.json (allowlist approach, more secure than .npmignore):
{
"files": [
"dist/",
"README.md",
"LICENSE"
]
}
3. Script Injection Check
Verify package.json scripts have not been tampered with:
bun pm info . | grep -E "(preinstall|postinstall|prepack|postpack|prepare)"
Red flags in scripts:
preinstall that downloads and executes external scripts
postinstall with curl, wget, or fetch calls
- Scripts that reference URLs or IP addresses
- Scripts that read environment variables or
.npmrc
- Obfuscated or minified script commands
4. Dependency Review
bun audit
bun pm ls
Package.json Security Configuration
Recommended Configuration
{
"name": "@scope/package-name",
"version": "1.0.0",
"files": ["dist/", "README.md", "LICENSE"],
"scripts": {
"build": "bun build src/main.ts --outdir dist --target bun",
"test": "bun test",
"typecheck": "tsc --noEmit",
"prepublishOnly": "bun run test && bun run typecheck && bun run build"
},
"publishConfig": {
"access": "public",
"provenance": true
},
"trustedDependencies": []
}
Key Points
- Use scoped packages (
@scope/name): Prevents dependency confusion attacks
files allowlist: Only publish what is necessary
prepublishOnly script: Runs tests and build before every publish
publishConfig.provenance: Enable provenance by default
- Empty
trustedDependencies: Do not trust install scripts by default
CI/CD Publishing Pipeline Security
Required Protections
| Protection | Implementation |
|---|
| Branch protection | Only publish from main or release tags |
| Environment protection | GitHub Environment with required reviewers |
| Action pinning | All uses: pinned to full commit SHA |
| Minimal permissions | contents: read, id-token: write only |
| Frozen lockfile | bun install --frozen-lockfile |
| Test gate | Tests must pass before publish |
| Timeout | timeout-minutes on all jobs |
Anti-Patterns to Avoid
on: push
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- run: bun publish
- uses: actions/checkout@v4
Version Management
Safe Versioning Practices
bun version patch
bunx npm publish --tag next
Detecting Unauthorized Publishes
Monitor your packages for unexpected version bumps:
bunx npm info <package> time
In the Shai-Hulud attack, the worm performed patch version increments to make compromised publishes look like routine bug fixes.
Multi-Maintainer Security
Package Access Control
bunx npm access ls-collaborators <package>
bunx npm team create <org>:<team>
bunx npm access grant read-write <org>:<team> <package>
Rules for Multi-Maintainer Packages
- Require 2FA for all maintainers
- Use granular tokens scoped to specific packages
- Prefer Trusted Publishing (OIDC) over individual tokens
- Monitor access changes - set up alerts for new maintainer additions
- Review publish history regularly
Emergency Response: Package Compromise
If your published package has been compromised:
-
Unpublish the compromised version (within 72 hours of publish):
bunx npm unpublish <package>@<version>
-
Revoke ALL npm tokens immediately:
bunx npm token revoke <token-id>
-
Rotate GitHub tokens and secrets
-
Publish a clean patch version from verified source
-
Notify downstream users via GitHub Advisory
-
Report to npm security: security@npmjs.com
References