Secure npm package
Contract
| Field | Bound contract |
|---|---|
| Trigger | User wants to create, publish, or harden an npm package release using Trusted or Staged Publishing. |
| Authority | Reversible local: writes only named workflow drafts, project config files, and the handoff document; rollback is version control. No remote mutation, credential removal, tag creation, or publish. |
| Side effect | Writes hardened publish.yaml and check-workflows.yaml drafts, configures cooldown and postinstall-disablement in project config files, and lists every manual setting change in a human handoff. |
| Done | Both workflow drafts exist, cooldown and postinstall settings are written and read back, the handoff lists every human action with exact values, and no remote state changed. |
Inputs
- Root
package.json(required): read first to determine monorepo shape, package manager, build script, and repository URL. - GitHub owner/repo (required): parsed from the
repositoryfield, normalized fromgit+https://github.com/owner/repo.git,github:owner/repo, orgit remote get-url origin. Ask the user if none resolves. - Package-manager identity (required): from
packageManagerfield or lockfiles. - Per-package published state (required):
npm view <name> versionper public package. E404 means not yet published. - Existing workflows (required): read every
.github/workflows/*.ymlto detect existing release workflows andsecrets.NPM_TOKENusage.
Procedure
- Gather facts read-only. Read the root
package.json. If it hasworkspacesorpnpm-workspace.yamlexists, enumerate every workspacepackage.jsonwithout"private": true. Resolve the GitHub owner/repo from therepositoryfield; fall back togit remote get-url origin; ask the user if neither exists. Determine whether the owner is an org or personal account. Extract the package manager frompackageManageror lockfiles. Runnpm view <name> versionfor every public package; E404 means not yet published. Rungit tag --sort=-creatordate | headto detect existing tag format; default tov1.0.0if empty. Read every.github/workflows/*.ymlfile to detect existing release workflows andsecrets.NPM_TOKENusage. Done when: all facts are gathered. - Ask all decisions in one round. Collect every decision needed before any mutation: cooldown length (1 day or 3 days), whether build tools should move into
dependenciesfor the--omit=devmonorepo hack, therepositoryfield if missing, and any project-specific question the facts raise. Do not drip questions. Wait for all answers. Done when: all decisions are collected. - Present the manual-settings checklist with exact values and URLs, partitioned by package state. For every published package: list the npmjs.com Trusted Publisher entry URL with stage-only enforcement and the token revocation URL, with exact values (owner, repo,
publish.yaml, empty environment). For every E404 package: list the first-publish command (npm publish --ignore-scripts, add--access publicfor scoped packages) with interactive 2FA, and note that Trusted Publisher configuration is sequenced after that first publish. On github.com: org or personal 2FA confirmation, tag ruleset creation, and immutable releases enablement. Ifsecrets.NPM_TOKENwas found in a workflow, include deletion of that secret and token revocation. Wait for explicit confirmation of the applicable settings. Published packages require Trusted Publisher confirmation now; E404 packages require only the first-publish acknowledgment, since Trusted Publisher cannot be configured until after the first publish. Done when: the user explicitly confirms the applicable settings. - Write
publish.yamlandcheck-workflows.yaml, execute the cooldown config, and configure postinstall disablement. Writepublish.yamlwith these rules: trigger on version tags matching the detected tag format; separatetest,build, andpublishjobs where onlypublishreceivesid-token: writeand all jobs receivecontents: readandpersist-credentials: false; thepublishjob installs no dependencies and runsnpm stage publish --ignore-scripts(usenpmeven when the project uses pnpm, yarn, or bun, unless the package relies on a pnpm-only feature); every action pinned by full SHA commit hash; remove anysecrets.NPM_TOKENreference; for a monorepo, runnpm stage publish --ignore-scripts --workspacesor--workspace=<name>per independently-tagged package; for E404 packages, do not enable this workflow until after the first manual publish and Trusted Publisher setup, and the handoff lists the first-publish command and the Trusted Publisher sequence. Writecheck-workflows.yamlwith the zizmor lint workflow triggered on push tomainand on all pull requests. Execute the cooldown config command matching the detected package manager:npm config set --location=project min-release-age 3(npm),pnpm config set --location=project minimumReleaseAge 4320(pnpm 11+),yarn config set npmMinimalAgeGate 3d(yarn), orminimumReleaseAge = 259200under[install]inbunfig.toml(bun). Done when the setting is written to project config, verified by reading it back. Configure postinstall disablement: if npm 12+, pnpm 10+, yarn 4.14+, or bun, confirm no additional config is needed; otherwise addnpm config set --location=project ignore-scripts trueoryarn config set enableScripts false. Done when: postinstall disablement is configured or confirmed unnecessary. - Run zizmor until clean and write the handoff. Execute
docker run --rm -t -v "$(pwd):/repo:ro" ghcr.io/zizmorcore/zizmor:latest /repo/.github/workflowsif Docker is available; otherwise instruct the user to run it and paste the output. Fix every finding in existing workflows. Re-run until clean. Writepublish-setup-handoff.mdin the project root covering every human-only action with exact values and direct URLs: per-package Trusted Publisher entries (or first-publish command for E404 packages with Trusted Publisher sequenced after), token and secret deletion, 2FA enforcement, tag ruleset, immutable releases, and the tag-to-approve release flow. Done when: zizmor runs clean and the handoff is written.
Failure and recovery
| Failure | Recovery |
|---|---|
package.json not found or unreadable |
Stop. Cannot determine package names, repository URL, or build shape. |
repository field absent and git remote unavailable |
Stop. Cannot resolve owner/repo or generate checklist URLs. |
| Unconfirmed applicable settings | Stop before any write. Do not proceed to step 4. |
| Verification error from npmjs.com or github.com | Stop. Re-ask the user to confirm the settings. Do not assume they are complete. |
| Package manager not detected | Stop. Cooldown and publish commands depend on the package manager. |
| zizmor finds vulnerabilities | Fix every reported finding before concluding. Do not suppress. |
Rollback: any local file written by this skill is reversible by reverting the corresponding commit. No remote state is changed.
Output
.github/workflows/publish.yaml (hardened CI release workflow draft), .github/workflows/check-workflows.yaml (zizmor lint workflow), project config files with cooldown and postinstall settings written and read back, and publish-setup-handoff.md (human handoff listing every Trusted Publisher entry, 2FA enforcement, tag ruleset, immutable releases, token revocation, first-publish command for E404 packages, and the tag-to-approve release flow). No credential is removed, no remote setting or tag is changed, no release is published.