npm trust github Fails with 400 Bad Request: The Missing Permission Flag
A bare 400 with no error body, caused by a 20 May 2026 registry rule. The exact npm version boundary, the one flag that fixes it, and the npx workaround.
Founder, ibanchecker.cash
We published our JavaScript client to npm this week, and wiring up trusted publishing cost three failed attempts and two wasted two-factor approvals before the real cause turned up. The command failed with a bare 400 Bad Request and no explanation, in the CLI output and in the debug log alike. The fix is one flag. Finding out which flag took an hour, because the npm version that knows about it is the only thing that will tell you.
If you landed here from that error, the short answer is at the top of the next section. The rest explains why it happens, which npm versions are affected, and the two other traps waiting on the same path.
Why Does npm trust github Return 400 Bad Request?
Because your npm CLI is older than 20 May 2026 and is sending a trust configuration that no longer satisfies the registry. On that date npm started requiring every trusted publisher configuration to name at least one allowed action, and it shipped the CLI flags for it the same day. An older CLI does not know the field exists, sends the old payload, and the registry rejects it without saying what is missing.
The fix is to run a newer npm and pass the flag:
npm trust github \
--file publish.yml \
--repository your-org/your-repo \
--environment npm \
--allow-publishOn a current npm the same mistake is diagnosed properly. Instead of a 400 you get:
npm error At least one permission flag is required (--allow-publish, --allow-stage-publish)Which npm Versions Are Affected?
We pinned the boundary by running the same dry run against each version. It is exact:
- npm 11.14.1 (8 May 2026) and older: accepts the command, sends a payload with no permissions field, and the registry answers
400 Bad Requestwith no body. - npm 11.15.0 (20 May 2026) and newer: refuses client side and names the missing flag.
The machine we hit this on had npm 11.11.0, shipped in February 2026, three months before the rule changed. That is recent enough to feel current and old enough to fail silently, which is the worst combination.
Worth noting for anyone debugging this: nothing on the wire tells you either. The verbose log records http fetch POST 400 and the stack trace, and that is all. There is no error body to read, so the usual instinct of increasing log level does not help.
What If You Cannot Upgrade npm Globally?
This is a real corner. npm 12 requires Node 24.15.0 or newer, and if your Node is even slightly behind, the upgrade is refused:
npm error notsup Not compatible with your version of node/npm: [email protected]
npm error notsup Required: {"node":"^22.22.2 || ^24.15.0 || >=26.0.0"}
npm error notsup Actual: {"node":"v24.14.1","npm":"11.11.0"}A global install can also need elevated permissions you would rather not use for a one off command. You do not have to install anything. Run a newer npm directly:
npx -y [email protected] trust github \
--file publish.yml \
--repository your-org/your-repo \
--environment npm \
--allow-publishPick the newest npm that still supports your Node. The trust command talks to the registry, so it does not matter which npm your project normally uses.
Why Can OIDC Not Publish the First Version?
This is the trap before the trap. Trusted publishing is configured from an existing package’s settings page, so the package has to exist before OIDC can be attached to it, which means OIDC cannot perform a package’s first publish. You publish version one manually, then attach the trusted publisher, and every release after that goes through CI with no token.
If you have used PyPI, this will surprise you. PyPI lets you register a pending publisher for a name that does not exist yet, so the very first release can go out through OIDC with no manual step. npm has no equivalent.
One thing to be careful about when you read up on this: npm’s own trusted publishing documentation does not state the limitation either way. It simply assumes you already have a package. The constraint is documented in npm/cli issue 8544, which is open, and the publish-once-then-attach workaround is what every third party guide recommends. Do not write it up as confirmed by the npm docs, because it is not.
Why Is the Two-Factor URL Hidden?
Both npm publish and npm trust require a second factor, and when npm cannot open a browser it prints the approval URL for you to open yourself. Except in a non-interactive shell it redacts it:
npm error This operation requires a one-time password.
npm error Open this URL in your browser to authenticate:
npm error https://www.npmjs.com/auth/cli/***The redaction is in the debug log too, so there is nowhere to recover it from. If you are driving npm from a script, a CI shell, or any context without a TTY, allocate a pseudo-terminal and the URL comes through in full:
printf '\n' | script -q /dev/null npm publish --access public --auth-type=webThe printfanswers npm’s “Press ENTER to open in the browser” prompt, which makes it open the browser directly. Two practical notes: the approval link expires after a few minutes, so only run this when someone is actually at the keyboard, and the command sits waiting while it polls, so a short timeout will kill it before anyone can approve.
The Sequence That Works
For a brand new package, start to finish:
- Create the public repository and add a publish workflow triggered on
release: published, withid-token: writepermission and anenvironmentset. - Create that environment in the repository settings. It can be empty, with no protection rules.
- Publish version one manually with
npm publish --access public, approving the two-factor prompt. - Attach the trusted publisher with
npm trust github, including--allow-publish, approving two-factor again. - Every release after that publishes itself. No token is stored anywhere and no second factor is requested.
Verify with npm trust list <package>. A successful configuration prints the type, the repository, the workflow file, the environment and the permissions, so you can see exactly what the registry accepted.
One last thing on the first publish, since you only get one shot at it looking right: run npm publish --dry-run and read the file list. It is the cheapest way to catch a package that ships its source, its tests, or its lockfile because a files entry was missing.
Why We Were Doing This
The package that triggered all of this is @ibanchecker/client, the JavaScript and TypeScript client for the ibanchecker.cash API. It validates IBANs across 92 countries, resolves the bank behind an account, and looks up BIC codes, with no runtime dependencies and both ESM and CommonJS builds. There are Python and PHP clients as well, and those two publish through OIDC on their own registries, which is what made the npm difference so noticeable.
If you just want to check an IBAN without installing anything, the validator is free and needs no account.
Sources & References
- npm Docs: trusted publishing setup, allowed actions, and the 20 May 2026 change
- npm/cli issue 8544: allow publishing the initial version with OIDC
- npm/cli pull request 9248: add permissions support to trust commands
- GitHub Changelog: npm trusted publishing with OIDC is generally available
Last updated: August 2026
Integrate IBAN validation via API
REST API with 1,000 free validations/month. JSON response with bank name, SEPA status, and BBAN breakdown.
How to Validate IBANs in JavaScript (API & Manual)
Two approaches: call the ibanchecker.cash API for full bank data, or implement MOD-97 validation yourself. Includes work...
IBAN Validation in Python: Complete Implementation with Regex and Check Digits
Two production-ready approaches: call the ibanchecker.cash API for bank metadata, or implement the MOD-97 algorithm your...
Building an IBAN Validation API: REST Endpoint Design and Edge Cases
Architect-level guide to IBAN API design: GET vs POST tradeoffs, error code standards, normalization contracts, KV cachi...
IBAN Regex Patterns That Actually Work (And Why Simple Regex Isn't Enough)
Country-specific regex patterns for DE, GB, FR, NL plus a generic IBAN regex, and why regex is only step 1 of validation...