Communitygithub.com

SSBrouhard/npm-package-release-hardening

An agent skill that hardens npm packages before public release — catches shipped secrets, runtime-floor mismatches, and tarball junk. Works in any skills-compatible agent.

Qu'est-ce que npm-package-release-hardening ?

npm-package-release-hardening is a Claude Code agent skill that an agent skill that hardens npm packages before public release — catches shipped secrets, runtime-floor mismatches, and tarball junk. Works in any skills-compatible agent.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add SSBrouhard/npm-package-release-hardening

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

npm Package Release Hardening

Goal

Take an npm package from "works locally" to "honest, inspectable, and safe to publish." Prioritize real verification over vibes: package tarball contents, runtime contract, CI, GitHub settings, and dependency-update hygiene.

Start Here

  1. Confirm the exact repo:
pwd
git status --short --branch
git remote -v
  1. Inspect the package surface:
cat package.json
ls -la
find .github -maxdepth 3 -type f -print 2>/dev/null
  1. Detect the package manager from lockfiles:
  • package-lock.json -> npm
  • pnpm-lock.yaml -> pnpm
  • yarn.lock -> yarn

Use npm commands below as defaults, but translate to the detected package manager when needed.

Package Contract

Make package.json tell the truth:

  • name: publish target is intentional and available
  • version: current intended publish version
  • description, keywords: useful enough for registry search
  • license: present and matches LICENSE
  • repository, homepage, bugs: point at the public repo. If the public repo URL is not final yet, flag these as an explicit pre-publish TODO rather than silently leaving them out
  • type, exports, main, bin: match what users will import or run
  • files: limits the tarball to publishable artifacts
  • engines.node: minimum runtime the package actually supports
  • prepublishOnly or equivalent: prevents publishing stale build output

For CLI packages, confirm the bin target is built, executable where relevant, and smoke-tested through the command users will run.

Runtime Floor Rule

The Node engine is a promise, not decoration.

  • If engines.node says >=20, CI must test Node 20.
  • Keep @types/node on the same major line as the minimum supported runtime.
  • Prefer exact-pinning @types/node to the latest patch on that runtime line.
  • Do not merge @types/node major bumps above the runtime floor unless you also raise engines.node.
  • engines.node is advisory: npm only warns and does not hard-block install unless the user sets engine-strict, so CI on the floor version is what actually enforces the promise.

Why: Node types are dev-only, but they change what TypeScript allows. @types/node@26 can let code compile while using APIs that fail for users on Node 20.

Verification Commands

For npm projects:

npm ci
npm run build --if-present
npm test --if-present
npm audit --omit=dev
npm pack --dry-run

For pnpm/yarn, use the equivalent install/build/test/audit/pack commands. If audit support differs, say so instead of pretending.

For CLIs, also run at least one outside-in smoke test:

node dist/path/to/cli.js --help
npm pack --dry-run

If practical, install the packed .tgz in a temp directory and run the binary exactly as a user would.

Tarball Review

npm pack --dry-run is the registry truth surface. Check for:

  • includes built output
  • includes README.md and LICENSE
  • excludes source-only junk, temp files, tests, private docs, fixtures, local databases, secrets
  • package size is reasonable
  • CLI bin path appears in the tarball

If files is missing, add it. Do not rely on .gitignore as publish policy.

GitHub Baseline

Add or verify:

  • .github/workflows/ci.yml
  • .github/dependabot.yml
  • .github/CODEOWNERS
  • .github/pull_request_template.md
  • SECURITY.md

Use current supported major versions for GitHub-owned actions. If Actions warns that an action runtime is deprecated, update the action major after CI passes.

CI should usually include:

permissions:
  contents: read

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@CURRENT_MAJOR
      - uses: actions/setup-node@CURRENT_MAJOR
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build --if-present
      - run: npm test --if-present

Adjust node-version to the package's minimum supported runtime, and replace each @CURRENT_MAJOR with the action's current major tag (for example actions/checkout@v4) — pin to a real major, never commit the placeholder.

Dependabot Baseline

Configure both package and GitHub Actions updates:

version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
    open-pull-requests-limit: 5
    commit-message:
      prefix: deps
      include: scope

  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly
    open-pull-requests-limit: 5
    commit-message:
      prefix: deps
      include: scope

When Dependabot opens PRs immediately after config lands, treat that as proof the config is live. Triage the PRs; do not blindly merge all green checks.

Dependabot Triage

Use this rule of thumb:

  • Runtime dependency patch/minor: merge if checks pass and release notes are boring.
  • Runtime dependency major: inspect release notes and smoke-test real package behavior.
  • Dev tooling major: merge if build/test/pack pass and it does not alter the runtime contract.
  • @types/node above the runtime floor: close or replace with a pin to the runtime floor.
  • GitHub Actions major: merge if CI passes and it removes deprecation warnings.

If two PRs conflict in a small config file, merge one and update the other, or apply the second directly with a clear comment explaining it was superseded.

GitHub Remote Hardening

Apply what the repo/account supports, then verify the actual settings with gh or API reads.

Useful settings:

gh repo edit OWNER/REPO \
  --delete-branch-on-merge \
  --enable-squash-merge \
  --enable-merge-commit=false \
  --enable-rebase-merge=false \
  --allow-update-branch

gh api --method PUT repos/OWNER/REPO/vulnerability-alerts --silent
gh api --method PUT repos/OWNER/REPO/automated-security-fixes --silent
gh api --method PUT repos/OWNER/REPO/actions/permissions/workflow \
  -f default_workflow_permissions=read \
  -F can_approve_pull_request_reviews=false --silent

For Actions allowlists, prefer GitHub-owned actions only when the workflow uses only GitHub-owned actions. If third-party actions are required, explicitly allow the smallest set.

Public repos usually unlock more security controls. Private repos on free plans may block:

  • branch protection or rulesets
  • code scanning
  • secret scanning
  • push protection

Call blockers out plainly. Do not report unavailable controls as enabled.

Branch Protection

When available, protect the default branch with:

  • require pull request before merging
  • require CI status checks
  • require branches to be up to date if that fits the project
  • require conversation resolution
  • block force pushes
  • block deletions
  • require linear history if using squash-only merges

After enabling protection, stop direct-pushing to main unless the user explicitly asks and understands the bypass.

Gated PR Tools

If using an automated review/gate tool, make sure the remote has a default/base branch before asking the gate to create a PR. Empty new remotes often need an initial main push first; otherwise the gate can validate and push a feature branch but fail PR creation because there is no base ref.

Publish Checklist

Before npm publish:

  • npm whoami shows the intended account
  • npm view PACKAGE_NAME name version confirms name state
  • CI is green on the commit being published
  • npm pack --dry-run has been reviewed
  • README install/use examples match actual behavior
  • repository, homepage, and bugs resolve to the real public repo (or are explicitly flagged as TODO if the URL is not final)
  • no release-blocking Dependabot/security alerts remain
  • version bump is intentional, tagged in git (git tag vX.Y.Z && git push --tags), and reflected in a CHANGELOG or GitHub release
  • scoped packages (@scope/name) publish with --access public (scoped packages default to restricted/private)

For accounts using web/passkey/2FA auth:

npm publish --auth-type=web

Prefer publishing from CI with provenance for a verifiable supply-chain link. Provenance requires a public package, a supported CI such as GitHub Actions, and id-token: write permission on the job:

npm publish --provenance --access public

After publish:

npm view PACKAGE_NAME version
npm view PACKAGE_NAME bin files engines repository --json
npx -y PACKAGE_NAME --help

If published with provenance, confirm the registry shows the provenance / "Built and signed" attestation on the package page.

Final Response

Report only what was actually verified:

  • package checks run
  • tarball result
  • dependency PR decisions
  • commits/PRs created or merged
  • GitHub settings confirmed
  • controls blocked by plan/visibility
  • release tag and provenance status, if published
  • publish command or post-publish verification if still user-owned

Skills associés