.agents/skills/supply-chain-secure-publish/SKILL.md
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.
npx skillsauth add tacogips/claude-code-agent supply-chain-secure-publishInstall 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 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.
Apply these guidelines when:
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:
npm publish backdoored versions (patch bump)preinstall scripts pointing to setup_bun.js loader# Enable 2FA on your npm account (MANDATORY for all package maintainers)
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 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 |
When creating npm tokens:
# Create a granular token via npm CLI
npm token create --read-only # For CI install-only jobs
| 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 |
# Check if you have tokens in project .npmrc (should be EMPTY)
grep -r "authToken" .npmrc 2>/dev/null && echo "WARNING: Token found in project .npmrc!"
# Check home .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:
~/)Both locations are targeted for _authToken extraction.
Trusted Publishing eliminates stored npm tokens entirely. GitHub Actions authenticates directly with npm via OIDC.
Link your npm package to a GitHub repository on npmjs.com:
Create the publish workflow:
name: Publish Package
on:
release:
types: [created]
permissions:
contents: read
id-token: write # Required for OIDC
jobs:
publish:
runs-on: ubuntu-latest
environment: npm-publish # Optional: add environment protection rules
timeout-minutes: 10
steps:
- uses: actions/checkout@<SHA> # Pin to full SHA
with:
persist-credentials: false
- uses: oven-sh/setup-bun@<SHA> # Pin to full 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
# Publish with provenance (no NPM_TOKEN needed!)
- name: Publish to npm
run: bunx npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: "" # OIDC handles auth
| 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) |
Publishing with --provenance creates a verifiable link between the published package and its source code:
# Publish with provenance (in GitHub Actions with OIDC)
bunx npm publish --provenance --access public
Provenance proves:
# Check provenance of an installed package
npm audit signatures
Before every publish, verify:
# Preview what files will be published
bun pm pack --dry-run
# Or
bunx npm pack --dry-run
Verify:
.npmrc or credential files included.env files included.git directory includedUse files in package.json (allowlist approach, more secure than .npmignore):
{
"files": [
"dist/",
"README.md",
"LICENSE"
]
}
Verify package.json scripts have not been tampered with:
# Check for suspicious lifecycle scripts
bun pm info . | grep -E "(preinstall|postinstall|prepack|postpack|prepare)"
Red flags in scripts:
preinstall that downloads and executes external scriptspostinstall with curl, wget, or fetch calls.npmrc# Check for new or changed dependencies
bun audit
# Review dependency tree for unexpected additions
bun pm ls
{
"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": []
}
@scope/name): Prevents dependency confusion attacksfiles allowlist: Only publish what is necessaryprepublishOnly script: Runs tests and build before every publishpublishConfig.provenance: Enable provenance by defaulttrustedDependencies: Do not trust install scripts by default| 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 |
# BAD: Publishing from any branch
on: push
# BAD: Using npm token as global env
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
# BAD: No test gate before publish
steps:
- run: bun publish # No tests first!
# BAD: Unpinned actions
- uses: actions/checkout@v4 # Tag, not SHA!
# Bump version explicitly
bun version patch # or minor, major
# NEVER publish with --tag latest on pre-release
bunx npm publish --tag next # For pre-release versions
Monitor your packages for unexpected version bumps:
# Check recent publish history
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.
# List current maintainers
bunx npm access ls-collaborators <package>
# Use teams instead of individual access
bunx npm team create <org>:<team>
bunx npm access grant read-write <org>:<team> <package>
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: [email protected]
development
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.