| name | npm-package-publishing |
| description | Audit and improve the security posture of npm package publishing: account security, npm trusted publishing strategy, GitHub repository hardening, token removal, release governance, dependency update policy, provenance, and supply-chain risk. Use when the user asks about npm publishing best practices, publishing security, OIDC/trusted publishing strategy, npm token hygiene, release automation posture, Changesets versus changelog strategies, publint, provenance, or auditing an existing publishing pipeline. For writing or debugging the concrete GitHub Actions publish workflow file, use the npm-trusted-publishing-github-workflow skill instead.
|
npm Package Publishing — Best Practices
Based on the e18e publishing guide. Reference it for
the canonical source; this skill distils the actionable steps.
Agent Workflow
- Inspect the repository before recommending changes:
package.json
- lockfiles and
packageManager
.npmrc, .yarnrc.yml, .github/workflows/*
- release tooling such as Changesets, changelogithub, semantic-release, or release-it
- existing npm/GitHub tokens or
NODE_AUTH_TOKEN usage in workflows
- Classify the request:
- Audit: report risks by severity, with file and line references where possible.
- Implementation: make scoped repository changes, then run relevant validation.
- Strategy: explain tradeoffs and identify user-only settings.
- Separate agent-doable work from user-only UI work. Do not claim account, npmjs.com, or GitHub
settings are configured unless verified by authenticated tool/API access.
- For concrete GitHub Actions publish workflow creation or CI failure debugging, hand off to the
npm-trusted-publishing-github-workflow skill.
Freshness Rule
Before changing trusted publishing requirements, supported CI providers, npm CLI minimums, Node.js
minimums, provenance behavior, or GitHub release/security settings, verify the current official npm
and GitHub documentation when browsing is available. These requirements change over time.
Responsibility Split
Agent can usually:
- Edit
.github/workflows/*.
- Add or update repository package-manager config such as
.npmrc or .yarnrc.yml.
- Remove
NODE_AUTH_TOKEN usage from publish steps when trusted publishing is used.
If private dependencies require registry auth during install, keep that token scoped to read-only
install steps only.
- Add Dependabot/Renovate config.
- Run
publint, workflow linting, tests, package builds, and other local validation.
- Report exact user steps for npm/GitHub settings.
User or authenticated UI/API access required:
- Enable npm/GitHub 2FA.
- Configure npm trusted publisher settings on npmjs.com.
- Set package publishing access to require 2FA and disallow tokens.
- Enable GitHub repository/org Actions restrictions.
- Configure branch/tag rulesets and immutable releases.
- Remove repository/org secrets when the agent lacks GitHub settings access.
Non-Negotiables
- Do not add
NODE_AUTH_TOKEN to publish steps when trusted publishing is available.
Use OIDC for publishing; if private dependencies require install auth, use a read-only token only
for the install step.
- Do not store npm publish tokens in GitHub Actions secrets for OIDC-capable publishing.
- Do not run install lifecycle scripts in release workflows unless the user explicitly accepts the
risk.
- Do not show tag-pinned actions as compliant with SHA pinning; resolve all third-party and
GitHub-owned actions to full commit SHAs. Tag-based refs such as
@v6 or @v7 weaken
supply-chain integrity and violate pinned-action policy.
- Do not create or edit GitHub Actions workflows without explicit least-privilege
permissions:
for GITHUB_TOKEN. Set a restrictive top-level default such as contents: read, then grant
elevated scopes only on the jobs that need them.
- Do not use
actions/checkout with default credential persistence. Always set
persist-credentials: false; if a job must push to git, provide an explicit, narrowly scoped
credential only for that push step.
- Do not say npm/GitHub account settings are complete unless they were actually checked.
- Do not use self-hosted runners for npm trusted publishing unless official npm docs currently
support them.
Package manager note. The example install and configuration commands in this skill use npm
to match the e18e source material, but should be adapted to whichever package manager the project
already uses — pnpm, yarn, bun, etc. Adapt commands accordingly:
| npm | pnpm | yarn |
|---|
npm ci --ignore-scripts | pnpm install --frozen-lockfile --ignore-scripts | yarn install --frozen-lockfile --ignore-scripts |
npm i -g npm@12.0.1 | pnpm add -g pnpm@11.13.1 | yarn set version 4.17.1 |
ignore-scripts=true in .npmrc | ignore-scripts=true in .npmrc | enableScripts: false in .yarnrc.yml |
Detect the project's package manager by checking for a lockfile (pnpm-lock.yaml,
yarn.lock, bun.lock) or a packageManager field in package.json before
generating any commands or workflow steps.
Section 2.2's trusted-publishing requirement is npm-specific: the publish step must run with a
supported Node.js version and npm CLI version even when the rest of the workflow uses another
package manager.
For pnpm projects on GitHub Actions, prefer the Marketplace action
pnpm/setup to set up pnpm and
the JavaScript runtime in one step. It installs pnpm from @pnpm/exe, can read
devEngines.runtime from package.json, and replaces the separate
pnpm/action-setup + actions/setup-node pattern. In release workflows, pin pnpm/setup to a
full commit SHA, set install: false, and run the explicit hardened install command yourself so
flags like --frozen-lockfile --ignore-scripts remain visible.
1 · Prerequisites
1.1 · Enforce 2FA everywhere
| Account | Location | Recommended method |
|---|
| npm | Account → Security | Security key (YubiKey, Touch ID, Windows Hello); fallback: authenticator app |
| GitHub | Settings → Password and authentication | Same priority order |
Use a password manager with generated passwords for both accounts.
1.2 · Harden GitHub Actions settings
Settings → Actions → General:
- ✅ Require actions to be pinned to a full-length commit SHA
- ✅ Require approval for first-time contributors
- ✅ Set default workflow permissions to "Read repository contents and packages"
If the repository belongs to an organisation, apply these settings at org level for consistency
across all repositories.
1.3 · Configure branch protection
Settings → Branches → create a ruleset for main:
- ✅ Require a pull request before merging
- ✅ Require at least 1 approval
- ✅ Dismiss stale approvals when new commits are pushed
- ✅ Require approval of the most recent reviewable push
1.4 · Remove legacy npm tokens
Settings → Secrets & Variables → Actions: remove npm publish tokens. Retain any read-only
registry credential required for private installs, scoped to install steps only.
2 · Trusted Publishing (OIDC)
Trusted publishing means GitHub Actions authenticates directly with npm via OIDC — no npm token
ever touches the repository.
2.1 · Configure on npmjs.com
- Open your package page → Settings tab → Trusted Publishing section.
- Add a trusted publisher:
- Organisation / user: your GitHub org or username
- Repository: the repository name
- Workflow filename: e.g.
publish.yml
- Check "Require two-factor authentication and disallow tokens" — this forces manual publishes
to use 2FA as well.
For bulk configuration across many packages, use
open-packages-on-npm to open each package
in a new tab, then the
npm-trusted-publisher userscript
to configure them rapidly.
2.2 · npm CLI version requirement
The publish step must use npm CLI ≥ 11.5.1 for automatic OIDC trusted publishing.
Node.js 22.14.0 or newer can use trusted publishing when the workflow installs npm CLI 11.5.1 or
newer before publishing:
- run: npm i -g npm@12.0.1
3 · Standard Publish Workflow
Use the e18e setup-publish template
as your base, or scaffold it with:
npx @e18e/setup-publish@1.1.0
3.1 · Job structure
The workflow must separate build from publish. This ensures publish permissions (the OIDC
token) are never exposed to build-time code.
test → build → publish
3.2 · Non-negotiable workflow constraints
| Constraint | Why |
|---|
| All actions pinned to full-length commit SHA | Prevents supply-chain attacks via mutable tags |
Explicit least-privilege GITHUB_TOKEN permissions | Avoids inheriting broad repository defaults |
actions/checkout uses persist-credentials: false | Reduces token exposure to later workflow steps |
npm ci --ignore-scripts (or --ignore-scripts flag) | Prevents malicious lifecycle scripts running during install |
| Build and publish in separate jobs | Isolates publish permissions from arbitrary build code |
Always include a top-level permissions: block with the workflow-wide minimum, usually
contents: read. Add job-level permissions only when a job requires more access; for npm trusted
publishing, id-token: write belongs on the publish job only.
Every checkout step must disable persisted credentials:
- uses: actions/checkout@<full-length-commit-sha>
with:
persist-credentials: false
This is especially important for jobs that build packages or upload artifacts, because later steps
should not inherit a repository token unless they explicitly need one.
3.3 · Suppress lifecycle scripts project-wide
Add to .npmrc in the repository:
ignore-scripts=true
Also apply globally on developer machines:
npm config set -g ignore-scripts true
3.4 · Creating a release
git tag v1.0.0
git push origin v1.0.0
Then in GitHub UI: Releases → Draft a new release → choose the tag → Generate release notes
→ Publish release. This triggers the workflow.
4 · Alternative Workflow Strategies
4.1 · Changesets (recommended for teams)
Use the changesets template.
- Merged PRs automatically update a release pull request
- Changelog is generated by changesets and included in the release PR
- Releasing = merging the generated release PR — no manual tagging
4.2 · changelogithub (changelog from commit messages)
Use the changelogithub template.
- Tags are still pushed manually
- GitHub release + changelog are created automatically on tag push
- Package is published on tag push
5 · Ongoing Maintenance
5.1 · Keep dependencies updated
Set up Dependabot or Renovate to receive automated PRs for dependency updates,
addressing security vulnerabilities promptly.
5.2 · Keep GitHub Actions updated
All third-party and GitHub-owned actions must be pinned to a full commit SHA, not a tag. Tag refs
such as @v6 or @v7 are mutable and must not be committed to workflow files. To migrate existing
workflows and keep them current:
npx actions-up@1.16.0
Run this periodically, or let Dependabot/Renovate manage action updates once SHAs are in place.
5.3 · Lint workflows for vulnerabilities
zizmor detects template injection vulnerabilities and
excessive permission scopes in GitHub workflow files:
zizmor .github/workflows/publish.yml
Integrate this into CI or run it before merging workflow changes.
5.4 · Validate package.json and exports
publint checks for common publishing issues: missing files, incorrect
exports fields, wrong main/module paths, and more.
npx publint@0.3.21
Review the full list of publint rules to understand what it checks.
Run this locally before tagging a release.
5.5 · Visualise dependency changes
multiocular shows exactly what code changed
between dependency versions, helping catch unexpected changes or potential security issues.
6 · Further Security Hardening
6.1 · Use a GitHub environment (important for shared repos)
Without a GitHub environment, any account with write access can trigger a publish by creating
a branch and modifying the release workflow — bypassing code review entirely.
Settings → Environments:
- Create an environment named
publish.
- Do not allow administrator bypass of protection rules.
- Limit deployment to explicit branch names only (e.g.
main, v1). Do not use wildcards.
Remove stale branches promptly.
- Update the publish job in
publish.yml:
jobs:
publish:
environment: publish
- Update the npm trusted publisher settings on
npmjs.com to include the environment name.
Optionally configure the publish environment to require manual approval before the job
proceeds — providing a human gate even if malicious code reaches a release branch.
6.2 · Consider hardware security keys (optional)
Physical security keys (YubiKey, etc.) are significantly more resistant to phishing and credential
theft than authenticator apps or SMS. They are worth recommending but should never be enforced —
not everyone has access to one, and a good authenticator app is a perfectly reasonable alternative.
Mention this as a suggestion, not a requirement.
6.3 · Protect all long-lived branches and tags
Apply branch protection rules to every long-lived branch. Protect release tags separately with a
tag ruleset; branch protection does not apply to tags.
6.4 · Enable immutable releases
Enable GitHub immutable releases
to prevent modification or deletion of tags and releases after creation.
7 · Sole Maintainer Considerations
There is an open feature request to
support 2FA for GitHub environment approvals. Until that lands, trusted publishing carries a risk
for solo maintainers: a leaked GitHub token could be used to publish through the trusted workflow
without a 2FA challenge.
Recommendation for sole maintainers: consider publishing locally with npm publish and
2FA-protected npm access until environment-level 2FA approval is supported. Continue to follow
all other security recommendations in this document regardless.
Audit Output Format
For audits, lead with findings:
- P0/P1/P2/P3 severity.
- File and line when repo-local.
- Risk.
- Recommended fix.
- Whether the agent can implement it now or the user must configure it externally.
Then include:
- Validation run.
- Remaining user-only checklist.
- Suggested next change.
Quick Reference Checklist
Use this when setting up a new package or auditing an existing one.
Account security
Repository settings
Trusted publishing
Workflow hygiene
Package quality