DEV Community

Arnold Cartagena
Arnold Cartagena

Posted on

npm Trusted Publishing with GitHub Actions OIDC — What the Docs Don't Tell You (Scoped Packages)

After the recent npm supply chain attacks, long-lived tokens are out. Trusted publishing via OIDC is the way forward. But if you maintain scoped packages (@org/package), you're going to hit some walls the docs don't warn you about.

I spent a full day getting @edictum/openclaw to publish via trusted publishing from GitHub Actions. Here's everything I learned so you don't have to.

The Setup

I maintain Edictum, a runtime contract enforcement library for AI agents. We just shipped a native OpenClaw plugin and needed to publish @edictum/openclaw to npm from GitHub Actions using trusted publishing — no tokens, no secrets.

What Worked

Here's the final working workflow:

name: Publish to npm

on:
  release:
    types: [published]

permissions:
  id-token: write
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - uses: pnpm/action-setup@v4

      - uses: actions/setup-node@v5
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
          cache: pnpm

      - run: pnpm install --frozen-lockfile

      # THIS IS CRITICAL — bundled npm doesn't support OIDC for scoped packages
      - run: npm install -g npm@latest

      - run: pnpm build
      - run: pnpm test

      - run: npm publish --provenance --access public
Enter fullscreen mode Exit fullscreen mode

And in package.json:

{
  "publishConfig": {
    "access": "public",
    "provenance": true,
    "registry": "https://registry.npmjs.org/"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/edictum-ai/edictum-openclaw.git"
  }
}
Enter fullscreen mode Exit fullscreen mode

The Problems (and Fixes)

1. Bundled npm doesn't support OIDC for scoped packages

This is a known issue. The npm version bundled with Node.js 22 on GitHub-hosted runners is too old. You must upgrade:

- run: npm install -g npm@latest
Enter fullscreen mode Exit fullscreen mode

The npm docs say you need npm 11.5.1+. Without this, you get:

npm error code E404
npm error 404 Not Found - PUT https://registry.npmjs.org/@scope%2fpackage - Not found
Enter fullscreen mode Exit fullscreen mode

This E404 is misleading — the package exists, but the old npm can't do the OIDC token exchange for scoped packages.

2. actions/setup-node injects a default token that breaks OIDC

When you use registry-url with actions/setup-node, it automatically sets NODE_AUTH_TOKEN to ${{ github.token }}. This GitHub token overrides the OIDC flow, and npm tries to authenticate with it instead of doing the OIDC exchange.

Some people clear it with NODE_AUTH_TOKEN: "", but that breaks authentication entirely (ENEEDAUTH). Others remove registry-url, but then npm doesn't know where to publish.

The fix that actually works: upgrade npm (step 1 above). The upgraded npm correctly handles the OIDC flow even with the injected token present. The --provenance flag triggers the OIDC path explicitly.

3. The environment field on npmjs.com must match (or be empty)

When configuring the trusted publisher on npmjs.com, there's an "Environment name" field. If you set this to npm or release, the GitHub Actions job must run in a matching GitHub environment.

What tripped me up: I set the environment name on npmjs.com to npm and added environment: npm to my workflow job. It still failed with E404. Removing the environment name from npmjs.com (leaving it blank) fixed it immediately.

If you need environment-based protection (approval gates, etc.), make sure the names match exactly — case-sensitive, no trailing spaces. But if you don't need it, leave it blank.

4. --provenance is NOT automatic (despite what docs say)

The npm docs state:

When you publish using trusted publishing, npm automatically generates and publishes provenance attestations. You don't need to add the --provenance flag.

This was not my experience. Publishing without --provenance resulted in ENEEDAUTH. Adding it fixed the issue. You can also set it in package.json:

{
  "publishConfig": {
    "provenance": true
  }
}
Enter fullscreen mode Exit fullscreen mode

5. repository.url must match your GitHub repo exactly

The trusted publisher config on npmjs.com requires your org/repo. Your package.json must agree:

{
  "repository": {
    "type": "git",
    "url": "git+https://github.com/your-org/your-repo.git"
  }
}
Enter fullscreen mode Exit fullscreen mode

If these don't match, npm silently rejects the publish with a 404.

How Big Projects Handle This

Curious how others do it, I checked several major open-source projects:

Project Method
Vercel AI SDK NPM_TOKEN secret (token-based)
LangChain.js NPM_TOKEN secret + manual .npmrc
OpenClaw OIDC trusted publishing (non-scoped package)
shadcn/ui NPM_TOKEN secret

Most major projects still use token-based publishing. The ones using OIDC successfully tend to be non-scoped packages. Scoped packages with OIDC are still rough — npm/cli#8976 is open as of today.

The Checklist

If you're setting up trusted publishing for a scoped npm package from GitHub Actions:

  • [ ] npm 11.5.1+ (npm install -g npm@latest in your workflow)
  • [ ] permissions: id-token: write in your workflow
  • [ ] registry-url: https://registry.npmjs.org in actions/setup-node
  • [ ] --provenance flag on npm publish
  • [ ] repository.url in package.json matches your GitHub repo
  • [ ] publishConfig.access: "public" in package.json (for scoped packages)
  • [ ] Trusted publisher configured on npmjs.com → package → Settings
  • [ ] Environment name on npmjs.com: leave blank unless you specifically need it
  • [ ] No NODE_AUTH_TOKEN secret set in your repo (it would override OIDC)

The Result

@edictum/openclaw is now published with OIDC trusted publishing and provenance attestations, no long-lived secrets anywhere:

npm i @edictum/openclaw
Enter fullscreen mode Exit fullscreen mode

Or if you're an OpenClaw user:

openclaw plugins install @edictum/openclaw
Enter fullscreen mode Exit fullscreen mode

One command, zero code changes, 25 security contracts active. Check it out: github.com/edictum-ai/edictum-openclaw


Edictum is runtime contract enforcement for AI agent tool calls. Deterministic YAML contracts that execute outside the model — the LLM can't talk its way past them. Available in Python, TypeScript, and Go.

Top comments (0)