Introducing npm package provenance
How to verifiably link npm packages to their source repository and build instructions.
Starting today, when you build your npm projects on GitHub Actions, you can publish provenance alongside your package by including the --provenance
flag. This provenance data gives consumers a verifiable way to link a package back to its source repository and the specific build instructions used to publish it (see example on npmjs.com).
Under the hood we collect the following metadata about the source repository and specific build instructions:
_type: https://in-toto.io/Statement/v0.1
subject:
- name: pkg:npm/sigstore@1.2.0
digest:
sha512: 16bf7e5b59e40522190a425047b8c39ffcc8d145cdb15a69fbb9834240a764e2311bda7ac8d5c1c7dc67b47b1f532607139e570e4915577fab61bae4cc079eb0
predicateType: https://slsa.dev/provenance/v0.2
predicate:
buildType: https://github.com/npm/cli/gha/v2
builder:
id: https://github.com/actions/runner
invocation:
configSource:
uri: git+https://github.com/sigstore/sigstore-js@refs/heads/main
digest:
sha1: 5b8c0801d1f5d105351a403f58c38269de93f680
entryPoint: ".github/workflows/release.yml"
environment:
GITHUB_EVENT_NAME: push
GITHUB_REF: refs/heads/main
GITHUB_REPOSITORY: sigstore/sigstore-js
GITHUB_REPOSITORY_ID: '495574555'
GITHUB_REPOSITORY_OWNER_ID: '71096353'
GITHUB_RUN_ATTEMPT: '1'
GITHUB_RUN_ID: '4503589496'
GITHUB_SHA: 5b8c0801d1f5d105351a403f58c38269de93f680
GITHUB_WORKFLOW_REF: sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main
GITHUB_WORKFLOW_SHA: 5b8c0801d1f5d105351a403f58c38269de93f680
materials:
- uri: git+https://github.com/sigstore/sigstore-js@refs/heads/main
digest:
sha1: 5b8c0801d1f5d105351a403f58c38269de93f680
Check out our documentation for how to get started with the public beta and read on to learn more about why we built this!
Increasing trust in the npm supply chain
As home to the largest package registry in the world, GitHub is continually looking at security improvements to ensure the npm ecosystem remains healthy. Part of that responsibility is to help build trust in the open source projects we’re all building on top of. We want to give developers the tools they need to ensure the integrity of their software supply chain.
There is no single answer to the problem of software supply chain integrity. It requires a number of different solutions across the entire software development lifecycle. Until now, GitHub’s focus has primarily been on detecting and remediating accidental vulnerabilities. As the tools for dealing with vulnerable dependencies improve, attackers increasingly target other weaknesses in the software supply chain. Unwilling to wait for exploitable vulnerabilities to be disclosed, these attackers instead attempt to inject malicious code into projects by directly compromising popular dependencies.
While this sort of attack is relatively rare in comparison to the occurrence of unintentional vulnerabilities, we are seeing a growing number of these, and the impact is often magnified given that it’s a deliberate and targeted attack. Over the past few years there have been a number of notable attacks against popular npm packages, including UAParser.js, Command-Option-Argument, and rc.
Attacks like these are seldom carried out by compromising the source code directly, but are more often the result of compromised credentials that are then used to publish a malicious version.
The intrinsic transparency of the open source model instills a good degree of trust in the source code itself. The fact that we can all see the source and audit any changes decreases the chance that malicious code remains undetected. However, trust in the source code does not translate into trust in the published package.
In order to increase the level of trust you have in the npm packages you download from the registry you must have visibility into the process by which the source was translated into the published artifact.
Our goal for the npm ecosystem is to bring the same level of transparency we have with the open source code itself to the process by which that code is built and published.
Linking packages to their source and build
You probably wouldn’t pick up a random flash drive you found on the street and plug it into your laptop, but we regularly do exactly this with open source packages. Every day, developers pick packages off the npm registry and plug them into their applications without much thought.
Even if we decide we’re going to take the time to thoroughly examine each dependency we consume, packages contain very little information about where it came from and how it was made.
Most package pages on the npm registry have a link to a source repository, but this information isn’t verified and doesn’t point at any specific commit. With the code explorer you can view the contents of a package before you install it, but this doesn’t help you determine where it came from.
What we need is a way to draw a direct line from the npm package back to the exact source code commit from which it was derived. Much like an art historian tracking the chronology of ownership for a painting, we need a statement of provenance for a package which provides a verifiable record of the originating source and the build steps which were used to assemble the final artifact.
The Supply-chain Levels for Software Artifacts, or SLSA, specification was created for exactly this purpose and is what we’re using for our npm provenance statements. The SLSA provenance schema describes a subject
(our published npm package) as originating from input materials
(the source repository and commit SHA) which were processed by a buildConfig
(the steps executed to build/publish the package). These three values give us precisely what we need to understand how the published package was derived from the source.
Anchor trust in code
So, we have an idea of the information we’d like to capture and a format for recording it. The next step is to create a verifiable signature over the provenance statement, but who do we trust to attest to the provenance of a package?
Package signing is commonly done with a key which is managed directly by the maintainer. This allows consumers to verify that a package was indeed produced by the owner of that key. However, this approach is vulnerable to key compromise and doesn’t provide any verifiable way to link the source code to the published package.
Rather than relying on individual maintainers for signing, our aim is to anchor trust directly in the source code and the build process.
To achieve this, we require that packages are built on a trusted CI/CD platform. This provides visibility to the specific commit which triggered the build and the instructions which were used to publish the final artifact. With that information we increase the auditability of the build and make any attempt to tamper with the code much more visible.
Furthermore, we can use the identity of the CI environment and job (in the form of an OpenID Connect token) to apply a cryptographic signature to the provenance statement–attesting to the validity of the data in a way that can be verified by any consumers of the package.
To sign the provenance, we leverage the tools provided by the Sigstore project. Sigstore runs a public certificate authority which accepts an OIDC token from any conforming CI/CD provider and issues a short-lived, X.509 signing certificate in response.
As part of the package provenance generation, we create a single-use keypair to sign the provenance statement and then make a call to Sigstore’s Fulcio CA requesting a signing certificate which binds that key to the identity of the CI job. No one needs to manage the key (it’s deleted as soon as the signature is generated) but anyone presented with the signing certificate can verify the signature and also see the identity of the CI job responsible for creating it.
In order to leverage Sigstore’s public certificate authority you must be running on a supported cloud CI/CD provider. Today, we support GitHub Actions, but are working to drive support across as many CI/CD platforms as possible.
In summary, the following steps are performed when publishing with provenance from a supported CI/CD provider:
Verification
Generating provenance for a package is only half of the story. To be truly impactful, there must also be tools which allow consumers to verify that provenance was attested by a trusted source.
As part of the signing process, the provenance attestation is uploaded to Sigstore’s Rekor service. This public, tamper-evident transparency log makes it possible to detect if someone later attempts to modify the provenance or the contents of an already published package.
After the provenance attestation is posted to Rekor, it’s sent to the npm registry alongside the package being published. The registry checks the signature and the identity attached to the signing certificate to ensure that no one is trying to spoof the provenance before accepting the published version.
Packages published with provenance will be shown with a new badge next to the version number in the npmjs.com UI:
Developers can also use the npm CLI (available in npm
9.5.0+) to verify the integrity of provenance attestations for installed dependencies:
npm audit signatures
Looking ahead
As we move to make npm package provenance generally available we’re working on a number of additional improvements, including:
- Adopting version 1.0 of the SLSA provenance specification
- Working with other cloud CI/CD providers to add support for provenance signing
- Verifying the expected source repository and commit exist
- New tools to manage access between your CI/CD environment and the npm registry
Preventing deliberate supply chain attacks is not something we can do alone. GitHub is a founding member of the OpenSSF and actively participates in the working group for securing software repositories, with the goal of bringing similar capabilities to other platforms and package ecosystems. As an industry we can work together and mobilize around these efforts to secure the open source supply chain.
Tags:
Written by
Related posts
Execute commands by sending JSON? Learn how unsafe deserialization vulnerabilities work in Ruby projects
Can an attacker execute arbitrary commands on a remote server just by sending JSON? Yes, if the running code contains unsafe deserialization vulnerabilities. But how is that possible? In this blog post, we’ll describe how unsafe deserialization vulnerabilities work and how you can detect them in Ruby projects.
10 years of the GitHub Security Bug Bounty Program
Let’s take a look at 10 key moments from the first decade of the GitHub Security Bug Bounty program.
Where does your software (really) come from?
GitHub is working with the OSS community to bring new supply chain security capabilities to the platform.