Skip to content

Replace @semantic-release/npm with plain npm publish (@semantic-release/exec) to fix dependabot red runs and restore full-tree audit #32

Description

@lukeaus

Context

@semantic-release/npm depends on the npm CLI package (npm@11.x), which ships its dependencies bundled inside its tarball (undici 6.27.0, tar 7.5.19, ip-address 10.2.0, brace-expansion 5.0.7, ...). Bundled deps are version-pinned and cannot be fixed from this repo's lockfile.

Consequences, all of them recurring:

  • npm audit fails on Node 24 (and in the release workflow) with findings that exist only at node_modules/npm/**. The newest npm (12.0.2) bundles the same vulnerable set — there is no npm version with clean bundles, so this never self-heals.
  • Dependabot security-update runs fail permanently: "A patched version exists for undici, but the available update path still resolves it to 6.27.0" — the updater cannot construct a manifest fix for a bundled dep, so the job errors out. These runs recur on every advisory (undici, ip-address, tar, brace-expansion, js-yaml, ...).
  • Workarounds so far: CI/release audit gates were scoped to npm audit --omit=dev (audits the publishable tree; this library ships zero runtime deps).

Fix: remove the npm package from the dependency tree

@semantic-release/npm is the only path that pulls npm into the tree. Replace it with @semantic-release/exec (deps: debug, execa, lodash-es, parse-json, aggregate-error, @semantic-release/error — no npm) running custom commands, keeping the plugin's behavior via three phases:

Plugin behavior today Replacement
verifyConditions: registry auth (srnpm@13 does the GitHub-OIDC → npm token exchange internally — that's why release.yml asserts NODE_AUTH_TOKEN absent while ACTIONS_ID_TOKEN_REQUEST_URL is required) verifyConditionsCmd: explicit OIDC id-token exchange with npm's Trusted Publishing endpoint + npm whoami, failing closed before publish. Feed the token to npm via a temp .npmrc (never an env var), so the existing fail-closed token-absence evidence checks can stay.
prepare: bump package.json + package-lock.json to the computed next version (semantic-release core then commits it) prepareCmd: npm version ${nextRelease.version} --no-git-tag-version
publish: npm publish (provenance) publishCmd: npm publish --provenance (id-token: write already granted in release.yml)

Steps:

  1. Remove @semantic-release/npm, add @semantic-release/exec; update .releaserc.json plugin list.
  2. Add the OIDC exchange step (only piece with real unknowns — prototype and validate it first via the existing dry-run flow on a branch; never publish untested).
  3. Restore the audit gates to full-tree npm audit --audit-level=moderate in both ci.yml and release.yml (they pass again once npm is out of the tree).
  4. Verify with a release dry-run + rerun-idempotency check, then a real (no-op) release on master.

Not gaps (same behavior either way): dist-tag latest (no prerelease channels configured), access: public via publishConfig, files filtering, dry-run version capture, rerun idempotency, GitHub release creation (@semantic-release/github stays).

Payoff

  • Dependabot security-update red runs stop permanently (no bundled findings in the dependency graph).
  • Full-tree npm audit passes again — --omit=dev scoping can be reverted in both workflows.
  • Dependency graph and lockfile stop carrying the vendored npm CLI.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions