NPM Provenance: How to Get a Simple and Secure Release Pipeline

NPM Provenance: How to Get a Simple and Secure Release Pipeline

·

8 min read

By Alan Slater

NearForm’s Optic toolkit helps you gain NPM Provenance on securely released packages

NPM recently added support for ‘Provenance’ banners, thereby boosting package credibility. It achieved this by proving that a package was published from a particular commit and using a particular GitHub Actions pipeline.

It’s a positive step forward in trust and security in the open source JavaScript space, and at NearForm we’ve recently updated our free and open-source Optic release action (see also our previous post on Optic) to support it.

In this article, we’ll talk about how you can get an easy-to-use, secure release pipeline that generates provenance statements, either by adding provenance support to your own existing pipeline, or by using our Optic toolkit.

What you can do with NearForm Optic

When you’ve set up a pipeline like this, the release process becomes incredibly smooth and secure. For example, in NearForm Optic you can:

  • Press one button to generate a PR for your release, with an automatic changelog, links to issues and version bump

  • Automatically publish to NPM when the release PR is approved and merged

  • Require secure two-factor authentication via the Optic mobile app, to ensure a real, trusted team member has approved this release

  • And (new in 4.5.0) prove to all your package’s consumers viewing your package on NPM that this process has been correctly followed, with an auto-generated Provenance banner on your NPM package page.

Crucially, NPM’s new Provenance feature requires that the release has been done via a publicly-viewable GitHub Actions pipeline, so if you’re still publishing packages manually from your local machine’s terminal, now is a good time to consider upgrading to an automatic release pipeline, like Optic.

Adding provenance support if you use Optic

If you already have a release pipeline set up using NearForm Optic, you just have to make sure you’re on version 4.5.0 or greater, and add two lines to your release.yml file:

  • Add id-token: write under permissions

  • Add provenance: true under with at the end

An example configuration with this added can be found on the project readme.

If you’re new to Optic, the main usage documentation and example config now supports Provenance by default.

This PR for a real project is an example of how simple adding provenance support is to a project already using NearForm Optic.

Adding provenance support to another release pipeline

If you want to add Provenance to your own, existing release pipeline, it’s not hard to do, but you should know that NPM’s provenance feature is new and still in beta. There are a few possible pitfalls and quirks: here’s our advice on doing it right.

Minimum NPM versions

In NPM >= 9.5.0, running npm publish with the --provenance flag will attempt to publish your package with provenance, and cancel without publishing anything if the provenance part fails. There are various reasons it could fail, such as missing permissions, or missing or incorrect data in your package’s package.json file.

It’s good that NPM aborts the publication. Why? Because this error indicates an unexpected problem that you, as the package author, will want to fix. If it continued the publication, your package would lose its provenance banner and you’d have a release that you would want to replace.

However, npm publish doesn’t error on unexpected flags. So if your CI runner used a version of NPM prior to 9.5.0, that part will fail silently and it’ll publish a new release without provenance. If there was a provenance banner from a previous release, it’ll be lost from the package’s home page (remaining only on the release page for the previous release).

GitHub Actions’ popular ubuntu-latest image currently uses NPM 9.5.1, so supports Provenance by default. But not all Github Actions runners are as up to date, and NPM versions can change as a side effect of other configurations such as applying an older Node version. If a version of NPM prior to 9.5.0 is used, the release will continue and the provenance banner will silently and unexpectedly disappear.

In NearForm Optic, we include a check that the action is using a suitable version of NPM if provenance is requested, so all failures to generate provenance are caught. We would suggest either doing a similar check, or pinning a compatible version of NPM when adding provenance to your own release action.

Pass GitHub env vars (but not secrets!)

Internally, when NPM processes the npm publish --provenance instruction, it looks for metadata specific to GitHub Actions in the process’s environment variables. Firstly, it does so to ensure the action is in fact taking place within GitHub Actions, and then to verify details such as the action taken and the commit the action is acting on.

Those env vars will, of course, have to actually be present in the NPM child process. GitHub Actions’ own executor @actions/exec’s exec method (and many similar tools) automatically copy the parent’s process.env to the child unless an env object is provided. Forwarding everything by default like this is tempting and may appear to just work out of the box.

However, it’s important to be careful when inside a script run in a GitHub Action. All the inputs defined in your release.yml file’s with section will be present as snake-case environment variables with the prefix INPUT_ and these inputs frequently include confidential repo secrets, often including access tokens.

Even GitHub’s own exec tool forwards all these by default. For example, if your release.yml includes secret-token: ${{ secrets.TOKEN }}, a token so secret your own administrator can’t simply view it, then it might leak and become accessible to third-party scripts inside the child process, as process.env.INPUT_SECRET_TOKEN.

When writing scripts to be run inside a GitHub Action, it’s a good practice to ensure these aren’t forwarded. We can probably trust NPM to not do anything malicious with our secrets (if we can’t, we all have bigger problems!), but we can’t necessarily trust every imported script or child process they might internally invoke.

But how? Normally it would be a good practice to explicitly pass the child process only those environment variables we know it needs and we know we’re happy to share. But here, we know NPM expects full access to a full set of the environment variables GitHub Actions controls. If we pass a subset that works for the internal needs of this version of NPM and GitHub Actions, it risks being brittle against future changes to either tool.

The best solution is to pass a filtered version of process.env to the NPM publish child process, with any environment variable with the INPUT_ prefix removed. GitHub Actions by design delegates this namespace to users, and anything within it will be specific to our script and of no use to child processes, unless explicitly mapped and forwarded.

To ensure user secrets are secure in this way, NearForm Optic uses its own utility wrapper around @actions/exec that strips all repo inputs. It also displays output from the child process in a more developer-friendly way, making it clearer to see what went wrong if a release failed in a managed child process.

Misleading error messages

NPM 9.5.1, the version of NPM currently used in GitHub Actions’ ubuntu-latest image, contains a bug that could cost you some time if you make a mistake while configuring your pipeline. NPM provenance requires the id-token: write permission in your release pipeline’s yml configuration, which populates the ACTIONS_ID_TOKEN_REQUEST_URL environment variable.

In versions after 9.6.1, NPM gives a meaningful error if this part of the configuration isn’t correct, but in the version likely to be used by many or most GitHub Actions users today, it gives a very misleading error:

Automatic provenance generation not supported outside of GitHub Actions

If you see this error, and you are publishing from GitHub Actions, don’t lose time immediately trying to figure out why NPM failed to detect the publish action came from GitHub Actions. First, check your CI’s NPM version, and if it’s before 9.6.1, check your YML permissions and check environment variables (particularly, ACTIONS_ID_TOKEN_REQUEST_URL) are being correctly passed through to NPM.

In NearForm Optic, we check for this case specifically if the runner is using an affected NPM version, and give a correct error, to avoid sending users down the wrong debugging rabbit hole.

Issue linking

Any display of data can only ever be as good as the data it has to display. A crucial part of the new NPM Provenance banners is their link to the original release commit, so take a moment to check that the developer experience is a good one when a vigilant developer evaluates your package by following this link.

Ideally, your release commit should lead directly to a release PR, and that release PR should show clearly:

  • Who approved the release

  • Some form of changelog for this release

  • Links through to PRs and issues resolved by this release, to give context to the changelog

This kind of automatic cross-linking is important for developer experience, as well as for people inspecting your repository from other routes. How many times have you encountered what looks like a bug in a package, found appropriate issues discussing the fix, seen that they are closed and the issue appears resolved, but then had no clue if or when this fix was released beyond trawling through commits?

If your automatic release pipeline doesn’t already include it, please consider adding automatic changelogs, issue comments and cross-linking to the step that will be linked to from your Provenance banner. NearForm Optic has this already set up, with some extras like highlighting changes done by first-time contributors.

Tying it all together

Here’s an example of a release published by NearForm Optic using its new provenance support: @nearform/sql version 1.10.4 on NPM.

The commit link leads to a GitHub tree where the latest commit clearly links to a release PR. That PR lists a changelog of issues fixed, with details on who fixed them (highlighting that they were by first-time contributors). Each of those issues links back to the release PR that fixed it, clearly showing the relevant version number.

Each release is clear and transparent, making it much easier for a developer considering using the package to know what they are getting — earning justified trust.

If you don’t already have an automated release pipeline that meets these standards, please consider upgrading or giving NearForm Optic a try. If you pick the latter option and you or your business need some help setting up NearForm Optic in your project, please reach out and contact us — we’re always happy to talk with companies and discuss how we can level up their operations.

And most importantly: please continue being a great open source citizen.