├── .changeset ├── README.md ├── config.json └── wise-countries-tease.md ├── .eslintignore ├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── dependabot-changesets.yml │ ├── provenance.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── package-lock.json ├── package.json ├── packages ├── cli │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ ├── dev │ │ ├── dev.cmd │ │ ├── run │ │ └── run.cmd │ ├── oclif.manifest.json │ ├── package.json │ ├── src │ │ ├── commands │ │ │ └── generate.ts │ │ └── index.ts │ └── tsconfig.json ├── generate-provenance │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json └── verify-provenance │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ └── tsconfig.json ├── provenance-statement.json ├── tsconfig.base.json └── tsconfig.build.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.changeset/wise-countries-tease.md: -------------------------------------------------------------------------------- 1 | --- 2 | '@npmcli/verify-provenance': patch 3 | --- 4 | 5 | Publish version version of verify-provenance 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | **/__generated__ 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint", 6 | "prettier" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier" 13 | ], 14 | "rules": { 15 | "prettier/prettier": ["error"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | versioning-strategy: auto 8 | open-pull-requests-limit: 10 9 | groups: 10 | prod-deps: 11 | dependency-type: "production" 12 | update-types: 13 | - "minor" 14 | - "patch" 15 | dev-deps: 16 | dependency-type: "development" 17 | update-types: 18 | - "minor" 19 | - "patch" 20 | - package-ecosystem: "github-actions" 21 | directory: "/" 22 | schedule: 23 | interval: daily 24 | open-pull-requests-limit: 10 25 | groups: 26 | minor-patch: 27 | update-types: 28 | - "minor" 29 | - "patch" 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | pull_request: 7 | branches: ['main'] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | lint-source: 14 | name: Lint/build code 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout source 18 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3 19 | - name: Setup node 20 | uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 21 | with: 22 | node-version: 18 23 | cache: npm 24 | - name: Install dependencies 25 | run: npm ci 26 | - name: Run linter 27 | run: npm run lint:check 28 | - name: Run build 29 | run: npm run build 30 | 31 | run-tests: 32 | name: Run tests 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout source 36 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3 37 | - name: Setup node 38 | uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 39 | with: 40 | node-version: 18 41 | cache: npm 42 | - name: Install dependencies 43 | run: npm ci 44 | - name: Generate provenance statement 45 | run: DEBUG=* npm run generate --workspace=packages/cli -- README.md -o ../../provenance-statement.json 46 | - name: Upload artifact 47 | uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 48 | with: 49 | name: provenance-statement.json 50 | path: provenance-statement.json 51 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-changesets.yml: -------------------------------------------------------------------------------- 1 | name: "Dependabot Changesets" 2 | 3 | on: pull_request 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | changesets: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | if: ${{ github.repository_owner == 'npm' && github.actor == 'dependabot[bot]' }} 15 | steps: 16 | - name: Dependabot Changesets 17 | uses: feelepxyz/dependabot-changesets@088619209e26134e4817fc0e7aba82cfdc10373a # v1.1.2 18 | -------------------------------------------------------------------------------- /.github/workflows/provenance.yml: -------------------------------------------------------------------------------- 1 | name: SLSA Provenance 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | id-token: write 8 | 9 | jobs: 10 | provenance: 11 | name: Generate signed SLSA provenance with Sigstore 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Setup node 15 | uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 16 | - name: Generate dummy package 17 | run: echo "hello world" > pkg && tar -czvf pkg.tgz pkg 18 | - name: Generate provenance statement with package as attestation subject 19 | run: npx @npmcli/provenance-cli generate pkg.tgz -o provenance-statement.json 20 | - name: Sign provenance statement 21 | run: npx @sigstore/cli attest ./provenance-statement.json -o provenance.sigstore.json 22 | - name: Verify provenance statement 23 | run: npx @sigstore/cli verify provenance.sigstore.json 24 | - name: Upload artifact 25 | uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 26 | with: 27 | name: provenance-sigstore.json 28 | path: provenance.sigstore.json 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | release: 15 | name: Release package 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: write 19 | pull-requests: write 20 | id-token: write 21 | steps: 22 | - name: Checkout source 23 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3 24 | 25 | - name: Setup node 26 | uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 27 | with: 28 | node-version: 18.17.0 29 | registry-url: 'https://registry.npmjs.org' 30 | cache: npm 31 | 32 | - name: Install latest npm 33 | run: npm install -g npm@latest 34 | 35 | - name: Install dependencies 36 | run: npm ci 37 | 38 | - name: Create Release Pull Request 39 | uses: changesets/action@f13b1baaa620fde937751f5d2c3572b9da32af23 # v1.4.5 40 | with: 41 | publish: npm run release 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | .tool-versions 5 | hack/ca/store 6 | hack/ca/*.crt 7 | tsconfig.tsbuildinfo 8 | .vscode 9 | *.code-workspace 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true, 7 | "singleQuote": true 8 | } 9 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @feelepxyz 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: https://github.com/npm/provenance/fork 4 | [pr]: https://github.com/npm/provenance/compare 5 | [code-of-conduct]: CODE_OF_CONDUCT.md 6 | 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 8 | 9 | Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE.md). 10 | 11 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 12 | 13 | ## Submitting a pull request 14 | 15 | 1. [Fork][fork] and clone the repository 16 | 1. Configure and install the dependencies: `npm install` 17 | 1. Make sure the tests pass on your machine: `npm run test` 18 | 1. Make sure linter passes on your machine: `npm run lint` 19 | 1. Create a new branch: `git checkout -b my-branch-name` 20 | 1. Make your change, add tests, and make sure the tests and linter still pass 21 | 1. Push to your fork and [submit a pull request][pr] 22 | 1. Pat yourself on the back and wait for your pull request to be reviewed and merged. 23 | 24 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 25 | 26 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 27 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 28 | 29 | ## Resources 30 | 31 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 32 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 33 | - [GitHub Help](https://help.github.com) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # provenance 2 | 3 | Details on npm provenance 4 | 5 | ## Packages 6 | 7 | - [`@npmcli/provenance-cli`](./packages/cli) - Command line interface for generating SLSA provenance on supported CI/CD vendors. 8 | - [`@npmcli/generate-provenance`](./packages/generate-provenance) - Library for generating SLSA provenance on supported CI/CD vendors. 9 | - [`@npmcli/verify-provenance`](./packages/verify-provenance) - Library for verifying SLSA provenance from supported CI/CD vendors. 10 | 11 | ## Demo: Generating signed SLSA provenance 12 | 13 | ### GitHub Actions 14 | 15 | [`.github/workflows/provenance.yml`](https://github.com/npm/provenance/blob/main/.github/workflows/provenance.yml) 16 | 17 | ``` 18 | name: SLSA Provenance 19 | 20 | on: 21 | workflow_dispatch: 22 | 23 | permissions: 24 | id-token: write 25 | 26 | jobs: 27 | provenance: 28 | name: Generate signed SLSA provenance with Sigstore 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Setup node 32 | uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4 33 | - name: Generate dummy package 34 | run: echo "hello world" > pkg && tar -czvf pkg.tgz pkg 35 | - name: Generate provenance statement with package as attestation subject 36 | run: npx @npmcli/provenance-cli generate pkg.tgz -o provenance-statement.json 37 | - name: Sign provenance statement 38 | run: npx @sigstore/cli attest ./provenance-statement.json -o provenance.sigstore.json 39 | - name: Verify provenance statement (TODO: Verify source identity) 40 | run: npx @sigstore/cli verify provenance.sigstore.json 41 | - name: Upload artifact 42 | uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 43 | with: 44 | name: provenance-sigstore.json 45 | path: provenance.sigstore.json 46 | ``` 47 | 48 | ## Publishing with provenance 49 | 50 | ### Overview 51 | 52 | - [npm/cli](https://github.com/npm/cli) 53 | - [Generate provenance statement](https://github.com/npm/cli/blob/latest/workspaces/libnpmpublish/lib/provenance.js) 54 | - [Publish](https://github.com/npm/cli/blob/0dc63323f6566e6c94e03044c03d14f9a0a5142c/workspaces/libnpmpublish/lib/publish.js#L131-L164): Upload the signing certificate, signed provenance and package to the npm registry 55 | - [sigstore/sigstore-js](https://github.com/sigstore/sigstore-js) 56 | - [Sign package](https://github.com/sigstore/sigstore-js/tree/main/packages/sign) 57 | - Request workflow identity (OIDC ID token) from CI/CD 58 | - Create a temporary public/private key pair 59 | - Send a proof of private key possession, ID token and public key to Fulcio 60 | - Fulcio verifies and returns a signing certificate valid for 10 mins 61 | - Sign the provenance statement and uploads it to Rekor and deletes the keys 62 | 63 | ### Server-side provenance verification and proof 64 | 65 | npm performs server-side verifications and integrity checks on the provenance bundle before accepting the publish: 66 | 67 | - Validate the `Issuer` extension in the [signing cert](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726418--issuer-v2) is supported 68 | - Validate provenance was generated on a cloud-hosted runner by comparing the `Runner Environment` extension in the [signing cert](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264111--runner-environment) against allowed values 69 | - Validate provenance was generated on a public repository/project by comparing the `Source Repository Visibility At Signing` extension in the [signing cert](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264122--source-repository-visibility-at-signing) against allowed values 70 | - Verify extensions in the [signing certificate](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md) (non-falsifiable) match what's in the SLSA provenance statement ([generated in the npm/cli](https://github.com/npm/cli/blob/latest/workspaces/libnpmpublish/lib/provenance.js) and falsifiable by modifying the env vars during build) 71 | - Verify provenance was signed and uploaded to Sigstore: `sigstore.verify(provenanceBundle)` 72 | - Downloads the latest root certificate and public keys for Sigstore public good by using tuf-js 73 | - Verify the published package name, version (PURL) and tarball `sha-512` matches what's in the signed [provenance statement subject](https://github.com/npm/cli/blob/0dc63323f6566e6c94e03044c03d14f9a0a5142c/workspaces/libnpmpublish/lib/publish.js#L133-L136) 74 | - Verify the `repository` / `repository.url` in the uploaded `package.json` matches what's in the [signing certificate](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264112--source-repository-uri) `Source Repository URI` extension 75 | 76 | When verification is succesful npm attests the publish with by signing a [publish attestation](https://github.com/npm/attestation/tree/main/specs/publish/v0.1). This proves the registry accepted the published version /w proof on Rekor to keep the registry honest. 77 | 78 | Public signing keys for the signed `publish attestation` are distributed via the public [Sigstore Trust Root](https://github.com/sigstore/root-signing) in a target that matches the registry hostname: [registry.npmjs.org](https://github.com/sigstore/root-signing/tree/main/repository/repository/targets/registry.npmjs.org). 79 | 80 | This means another npm registry can distribute public keys using the same hostname scheme and these will be [discovered by the npm cli](https://github.com/npm/cli/blob/latest/lib/commands/audit.js#L199-L200) during verification. 81 | 82 | ## Verifying attestations with `npm audit signatures` 83 | 84 | ### Overview 85 | 86 | - [npm/cli](https://github.com/npm/cli) 87 | - [Audit command](https://github.com/npm/cli/blob/latest/lib/commands/audit.js) 88 | - [verifySignatures](https://github.com/npm/cli/blob/0dc63323f6566e6c94e03044c03d14f9a0a5142c/lib/commands/audit.js#L308-L328) 89 | - [pacote.manifest](https://github.com/npm/pacote/blob/a07758b200d8b16e2fcf639467b2ed7cfd9769c2/lib/registry.js#L209-L321) 90 | - Invokes `sigstore.verify` on each downloaded bundle 91 | - Verify the attestation matches the installed package name, version and tarball by comparing the in-toto attestation subject name and digest 92 | - Verify that the registry accepted the publish by an authorized user/token by verifying at least on attestation using a trusted public key from the sigstore trust-root 93 | - [sigstore/root-signing](https://github.com/sigstore/root-signing) 94 | - [npm's public keys](https://github.com/sigstore/root-signing/tree/main/repository/repository/targets/registry.npmjs.org) 95 | - [sigstore/sigstore-js](https://github.com/sigstore/sigstore-js) 96 | - [sigstore.verify](https://github.com/sigstore/sigstore-js/tree/main/packages/client#verifybundle-payload-options) 97 | - Update Sigstore trusted root using [tuf-js](https://github.com/theupdateframework/tuf-js) 98 | - Verify artifact signature using public key in signing certificate 99 | - Verify signing certificate was issued by Sigstore trusted root certificate 100 | - Verify Fulcio signed the certificate values/extensions at a given time (SCT) 101 | - Verify Rekor received the signed attestation while the signing certificate was valid 102 | 103 | ## Viewing provenance on npmjs.com 104 | 105 | - [Example package with provenance](https://www.npmjs.com/package/sigstore#provenance) 106 | 107 | ## Compatibility 108 | 109 | Summary of Sigstore provenance verification support in npm registry 110 | 111 | - The registry supports verifying sigstore v0.1 and v0.2 bundles generated by sigstore-js 1.0+ (from npm cli 9.5.0+) 112 | - Support verifying both slsa v0.2 and v1.0 provenance predicate's from GHA and slsa v0.2 provenance predicates from GitLab 113 | - Verify the latest version of the Fulcio Signing Certificate as any client sigstore combo will always get the latest one from Fulcio 114 | - See list of current [Fulcio signing cert extensions](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md) 115 | 116 | The following table documents which combinations of Sigstore bundle versions, `sigstore` client versions, `npm` CLI versions and SLSA predicate versions that are supported by the npm registry. 117 | 118 | | sigstore | 1.0..1.5 | 1.6 | 1.7..2.0 | 2.1 | 119 | | -------------- | ------------------ | ------------------ | ------------------ | ------------------ | 120 | | npm | 9.5.0..9.6.7 | 9.7.2 | 9.8.0 | 10.0.0 | 121 | | Bundle Version | | | | | 122 | | v0.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 123 | | v0.2 | | | | :white_check_mark: | 124 | | SLSA Predicate | | | | | 125 | | v0.2 (GHA) | :white_check_mark: | :white_check_mark: | | | 126 | | v1.0 (GHA) | | | :white_check_mark: | :white_check_mark: | 127 | | v0.2 (GitLab) | | :white_check_mark: | :white_check_mark: | :white_check_mark: | 128 | 129 | ## Adding support for new CI vendors 130 | 131 | - [npm/cli](https://github.com/npm/cli) 132 | - Generate a SLSA v1.0 (as of writing) provenance statement during build, [see example for GHA/GitLab](https://github.com/npm/cli/blob/0dc63323f6566e6c94e03044c03d14f9a0a5142c/workspaces/libnpmpublish/lib/provenance.js#L18-L195) 133 | - Check pre-requisites are met for provenance generation, [see example](https://github.com/npm/cli/blob/0dc63323f6566e6c94e03044c03d14f9a0a5142c/workspaces/libnpmpublish/lib/publish.js#L172-L223) 134 | - [See example for added gitlab support](https://github.com/npm/cli/pull/6526) 135 | - [sigstore/sigstore-js](https://github.com/sigstore/sigstore-js) 136 | - Mint an ID token, [see example for GHA/GitLab](https://github.com/sigstore/sigstore-js/blob/main/packages/sign/src/identity/ci.ts) 137 | - [See example for added gitlab support](https://github.com/sigstore/sigstore-js/pull/394) 138 | 139 | ## Updating the SLSA predicate 140 | 141 | - [npm/cli](https://github.com/npm/cli) 142 | - [Update provenance generation](https://github.com/npm/cli/blob/latest/workspaces/libnpmpublish/lib/provenance.js) 143 | - [See example PR for updating to SLSA v1](https://github.com/npm/cli/pull/6613) 144 | 145 | ## Resources 146 | 147 | - [Talk: Build provenance for package registries](https://docs.google.com/presentation/d/1OO86MsN4rHlL6i2rzoEkvql0vCpxjB-wQyGIpZQoQBg/edit) 148 | - [Blog: Introducing npm package provenance](https://github.blog/2023-04-19-introducing-npm-package-provenance/) 149 | - [Docs: Generating provenance statements](https://docs.npmjs.com/generating-provenance-statements) 150 | - [Article: Build Provenance for All Package Registries](https://repos.openssf.org/build-provenance-for-all-package-registries) 151 | - [Example: Publishing npm package with provenance](https://github.com/npm/provenance-demo) 152 | - [RFC: Link npm packages to the originating source code repository and build](https://github.com/npm/rfcs/blob/main/accepted/0049-link-packages-to-source-and-build.md) 153 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Thanks for helping make GitHub safe for everyone. 2 | 3 | # Security 4 | 5 | GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 6 | 7 | Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. 8 | 9 | ## Reporting Security Issues 10 | 11 | If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. 12 | 13 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 14 | 15 | Instead, please send an email to opensource-security[@]github.com. 16 | 17 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 18 | 19 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | ## Policy 30 | 31 | See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-provenance-monorepo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "npm provenance monorepo", 6 | "scripts": { 7 | "clean": "npm run clean --workspaces --if-present", 8 | "build": "tsc --build tsconfig.build.json", 9 | "pretest": "npm run build", 10 | "lint": "eslint --fix --ext .ts packages/**", 11 | "lint:check": "eslint --max-warnings 0 --ext .ts packages/**", 12 | "release": "npm run build && changeset publish" 13 | }, 14 | "license": "Artistic-2.0", 15 | "devDependencies": { 16 | "@changesets/cli": "^2.27.10", 17 | "@total-typescript/shoehorn": "^0.1.1", 18 | "@tsconfig/node18": "^18.2.2", 19 | "@types/node": "^20.8.9", 20 | "@typescript-eslint/eslint-plugin": "^6.8.0", 21 | "@typescript-eslint/parser": "^6.8.0", 22 | "eslint": "^8.57.1", 23 | "eslint-config-prettier": "^9.0.0", 24 | "eslint-plugin-prettier": "^5.0.1", 25 | "prettier": "^3.0.3", 26 | "shx": "^0.3.3", 27 | "typescript": "^5.2.2" 28 | }, 29 | "workspaces": [ 30 | "./packages/*" 31 | ], 32 | "engines": { 33 | "node": "^16.14.0 || >=18.0.0" 34 | } 35 | } -------------------------------------------------------------------------------- /packages/cli/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @npmcli/provenance-cli 2 | 3 | ## 0.0.3 4 | 5 | ### Patch Changes 6 | 7 | - aa17372: Use @npmcli/generate-provenance 8 | 9 | ## 0.0.2 10 | 11 | ### Patch Changes 12 | 13 | - 8663156: Publish a new version with provenance 14 | -------------------------------------------------------------------------------- /packages/cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | # @npmcli/provenance-cli 2 | 3 | CLI for generating SLSA provenance on GitHub Actions and GitLab CI. 4 | 5 | # Usage 6 | 7 | 8 | 9 | ```sh-session 10 | $ npm install -g @npmcli/provenance-cli 11 | $ provenance COMMAND 12 | running command... 13 | $ provenance (--version) 14 | @npmcli/provenance-cli/0.0.1 darwin-arm64 node-v18.17.1 15 | $ provenance --help [COMMAND] 16 | USAGE 17 | $ provenance COMMAND 18 | ... 19 | ``` 20 | 21 | 22 | 23 | # Commands 24 | 25 | 26 | 27 | - [`provenance generate [SUBJECT-PATH]`](#provenance-generate-subject-path) 28 | - [`provenance help [COMMANDS]`](#provenance-help-commands) 29 | 30 | ## `provenance generate [SUBJECT-PATH]` 31 | 32 | Generate SLSA provenance information on supported cloud CI/CD vendors. 33 | 34 | ``` 35 | USAGE 36 | $ provenance generate [SUBJECT-PATH] [--subject-name ] [--subject-digest ] [-o ] 37 | 38 | ARGUMENTS 39 | SUBJECT-PATH subject file to generate statement for 40 | 41 | FLAGS 42 | -o, --output-file= write output to file 43 | --subject-digest= Subject digest to use in statement 44 | --subject-name= Subject name to use in statement 45 | 46 | DESCRIPTION 47 | Generate SLSA provenance information on supported cloud CI/CD vendors. 48 | 49 | EXAMPLES 50 | $ provenance generate 51 | ``` 52 | 53 | ## `provenance help [COMMANDS]` 54 | 55 | Display help for provenance. 56 | 57 | ``` 58 | USAGE 59 | $ provenance help [COMMANDS] [-n] 60 | 61 | ARGUMENTS 62 | COMMANDS Command to show help for. 63 | 64 | FLAGS 65 | -n, --nested-commands Include all nested commands in the output. 66 | 67 | DESCRIPTION 68 | Display help for provenance. 69 | ``` 70 | 71 | 72 | -------------------------------------------------------------------------------- /packages/cli/bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const oclif = require('@oclif/core') 4 | 5 | const path = require('path') 6 | const project = path.join(__dirname, '..', 'tsconfig.json') 7 | 8 | // In dev mode -> use ts-node and dev plugins 9 | process.env.NODE_ENV = 'development' 10 | 11 | require('ts-node').register({project}) 12 | 13 | // In dev mode, always show stack traces 14 | oclif.settings.debug = true; 15 | 16 | // Start the CLI 17 | oclif.run().then(oclif.flush).catch(oclif.Errors.handle) 18 | -------------------------------------------------------------------------------- /packages/cli/bin/dev.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\dev" %* -------------------------------------------------------------------------------- /packages/cli/bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const oclif = require('@oclif/core') 4 | 5 | oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle')) 6 | -------------------------------------------------------------------------------- /packages/cli/bin/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\run" %* 4 | -------------------------------------------------------------------------------- /packages/cli/oclif.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "generate": { 4 | "aliases": [], 5 | "args": { 6 | "subject-path": { 7 | "description": "subject file to generate statement for", 8 | "name": "subject-path", 9 | "required": false 10 | } 11 | }, 12 | "description": "Generate SLSA provenance information on supported cloud CI/CD vendors.", 13 | "examples": [ 14 | "<%= config.bin %> <%= command.id %>" 15 | ], 16 | "flags": { 17 | "subject-name": { 18 | "description": "Subject name to use in statement", 19 | "name": "subject-name", 20 | "required": false, 21 | "hasDynamicHelp": false, 22 | "multiple": false, 23 | "type": "option" 24 | }, 25 | "subject-digest": { 26 | "description": "Subject digest to use in statement", 27 | "name": "subject-digest", 28 | "required": false, 29 | "hasDynamicHelp": false, 30 | "multiple": false, 31 | "type": "option" 32 | }, 33 | "output-file": { 34 | "aliases": [ 35 | "output", 36 | "out" 37 | ], 38 | "char": "o", 39 | "description": "write output to file", 40 | "name": "output-file", 41 | "required": false, 42 | "hasDynamicHelp": false, 43 | "multiple": false, 44 | "type": "option" 45 | } 46 | }, 47 | "hasDynamicHelp": false, 48 | "hiddenAliases": [], 49 | "id": "generate", 50 | "pluginAlias": "@npmcli/provenance-cli", 51 | "pluginName": "@npmcli/provenance-cli", 52 | "pluginType": "core", 53 | "strict": true, 54 | "enableJsonFlag": false, 55 | "isESM": false, 56 | "relativePath": [ 57 | "dist", 58 | "commands", 59 | "generate.js" 60 | ] 61 | } 62 | }, 63 | "version": "0.0.1" 64 | } 65 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@npmcli/provenance-cli", 3 | "version": "0.0.3", 4 | "description": "Generate SLSA provenance", 5 | "author": "Philip Harrison @feelepxyz", 6 | "license": "Artistic-2.0", 7 | "keywords": [ 8 | "cli", 9 | "npm", 10 | "provenance" 11 | ], 12 | "main": "dist/index.js", 13 | "types": "dist/index.d.ts", 14 | "bin": { 15 | "provenance": "bin/run" 16 | }, 17 | "files": [ 18 | "/bin", 19 | "/dist", 20 | "/oclif.manifest.json" 21 | ], 22 | "publishConfig": { 23 | "access": "public", 24 | "provenance": true 25 | }, 26 | "scripts": { 27 | "clean": "shx rm -rf dist", 28 | "prebuild": "npm run clean", 29 | "build": "tsc -b", 30 | "postpack": "shx rm -f oclif.manifest.json", 31 | "prepack": "npm run build && oclif manifest && npm run readme", 32 | "dev:bin": "npm run build && ./bin/run", 33 | "readme": "oclif readme --no-aliases && shx sed -i \"s/^_See code:.*$//g\" README.md", 34 | "generate": "npm run build && ./bin/run generate" 35 | }, 36 | "dependencies": { 37 | "@oclif/color": "^1.0.13", 38 | "@oclif/core": "^3", 39 | "@oclif/plugin-help": "^6", 40 | "ci-info": "^3.9.0", 41 | "@npmcli/generate-provenance": "^0.0.1" 42 | }, 43 | "devDependencies": { 44 | "@types/debug": "^4.1.10", 45 | "oclif": "^4.0.3", 46 | "tslib": "^2.6.1" 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "git+https://github.com/npm/provenance.git" 51 | }, 52 | "homepage": "https://github.com/npm/provenance/tree/main/packages/cli#readme", 53 | "bugs": "https://github.com/npm/provenance/issues", 54 | "oclif": { 55 | "bin": "provenance", 56 | "dirname": "provenance", 57 | "commands": "./dist/commands", 58 | "plugins": [ 59 | "@oclif/plugin-help" 60 | ], 61 | "topicSeparator": " " 62 | }, 63 | "engines": { 64 | "node": ">=18.0.0" 65 | } 66 | } -------------------------------------------------------------------------------- /packages/cli/src/commands/generate.ts: -------------------------------------------------------------------------------- 1 | import { Args, Command, Flags } from '@oclif/core'; 2 | import { createHash } from 'crypto'; 3 | import { createReadStream, existsSync } from 'fs'; 4 | import { writeFile } from 'fs/promises'; 5 | import { parse } from 'path'; 6 | import debug from 'debug'; 7 | import { generateProvenance } from '@npmcli/generate-provenance'; 8 | 9 | const DIGEST_ALGORITHM = 'sha256'; 10 | 11 | type Subject = { 12 | name: string; 13 | digest: Record; 14 | }; 15 | 16 | export default class Generate extends Command { 17 | static override description = 18 | 'Generate SLSA provenance statement on supported cloud CI/CD vendors.'; 19 | static override examples = ['<%= config.bin %> <%= command.id %>']; 20 | 21 | static override flags = { 22 | 'subject-name': Flags.string({ 23 | description: 'Subject name to use in statement', 24 | required: false, 25 | }), 26 | 'subject-digest': Flags.string({ 27 | description: 'Subject digest to use in statement', 28 | required: false, 29 | }), 30 | 'output-file': Flags.string({ 31 | char: 'o', 32 | description: 'write output to file', 33 | required: false, 34 | aliases: ['output', 'out'], 35 | }), 36 | }; 37 | 38 | static override args = { 39 | ['subject-path']: Args.file({ 40 | description: 'subject file to generate statement for', 41 | required: false, 42 | exists: true, 43 | }), 44 | }; 45 | 46 | public async run(): Promise { 47 | const bug = debug('provenance:generate'); 48 | const { args, flags } = await this.parse(Generate); 49 | 50 | const subject = await subjectFromInputs(args, flags); 51 | bug( 52 | `Generating provenance for ${subject.name} with digest ${JSON.stringify( 53 | subject.digest 54 | )}` 55 | ); 56 | const provenance = generateProvenance(subject, process.env); 57 | 58 | if (flags['output-file']) { 59 | bug(`Writing provenance statement to ${flags['output-file']}`); 60 | await writeFile( 61 | flags['output-file'], 62 | JSON.stringify(provenance, null, 2) 63 | ); 64 | } else { 65 | this.log(JSON.stringify(provenance, null, 2)); 66 | } 67 | 68 | return provenance; 69 | } 70 | } 71 | 72 | type Args = { 73 | 'subject-path': string | undefined; 74 | }; 75 | 76 | type Flags = { 77 | 'subject-name': string | undefined; 78 | 'subject-digest': string | undefined; 79 | 'output-file': string | undefined; 80 | } & { [flag: string]: string | undefined }; 81 | 82 | const subjectFromInputs = async ( 83 | args: Args, 84 | flags: Flags 85 | ): Promise => { 86 | if (args['subject-path'] && flags['subject-digest']) { 87 | throw new Error( 88 | 'Only one of subject-path or subject-digest may be provided' 89 | ); 90 | } 91 | 92 | if (args['subject-path']) { 93 | return getSubjectFromPath(args['subject-path'], flags['subject-name']); 94 | } else { 95 | if (!flags['subject-digest']) { 96 | throw new Error('One of subject-path or subject-digest must be provided'); 97 | } 98 | if (!flags['subject-name']) { 99 | throw new Error( 100 | 'subject-name must be provided when using subject-digest' 101 | ); 102 | } 103 | 104 | return getSubjectFromDigest(flags['subject-digest'], flags['subject-name']); 105 | } 106 | }; 107 | 108 | // Returns the subject specified by the path to a file. The file's digest is 109 | // calculated and returned along with the subject's name. 110 | const getSubjectFromPath = async ( 111 | subjectPath: string, 112 | subjectName?: string 113 | ): Promise => { 114 | if (!existsSync(subjectPath)) { 115 | throw new Error(`Could not find subject at path ${subjectPath}`); 116 | } 117 | const name = subjectName || parse(subjectPath).base; 118 | const digest = await digestFile(DIGEST_ALGORITHM, subjectPath); 119 | 120 | return { 121 | name, 122 | digest: { [DIGEST_ALGORITHM]: digest }, 123 | }; 124 | }; 125 | 126 | // Returns the subject specified by the digest of a file. The digest is returned 127 | // along with the subject's name. 128 | const getSubjectFromDigest = ( 129 | subjectDigest: string, 130 | subjectName: string 131 | ): Subject => { 132 | if (!subjectDigest.match(/^sha256:[A-Za-z0-9]{64}$/)) { 133 | throw new Error( 134 | 'subject-digest must be in the format "sha256:"' 135 | ); 136 | } 137 | const [alg, digest] = subjectDigest.split(':'); 138 | 139 | return { 140 | name: subjectName, 141 | digest: { [alg]: digest }, 142 | }; 143 | }; 144 | 145 | // Calculates the digest of a file using the specified algorithm. The file is 146 | // streamed into the digest function to avoid loading the entire file into 147 | // memory. The returned digest is a hex string. 148 | const digestFile = async ( 149 | algorithm: string, 150 | filePath: string 151 | ): Promise => { 152 | return new Promise((resolve, reject) => { 153 | const hash = createHash(algorithm).setEncoding('hex'); 154 | createReadStream(filePath) 155 | .once('error', reject) 156 | .pipe(hash) 157 | .once('finish', () => resolve(hash.read())); 158 | }); 159 | }; 160 | -------------------------------------------------------------------------------- /packages/cli/src/index.ts: -------------------------------------------------------------------------------- 1 | export { run } from '@oclif/core'; 2 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "importHelpers": true, 5 | "rootDir": "src", 6 | "outDir": "dist" 7 | }, 8 | "exclude": ["./dist"], 9 | "references": [ 10 | { "path": "../generate-provenance" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/generate-provenance/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @npmcli/generate-provenance 2 | -------------------------------------------------------------------------------- /packages/generate-provenance/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/generate-provenance/README.md: -------------------------------------------------------------------------------- 1 | # @npmcli/generate-provenance 2 | 3 | Library for generating SLSA provenance on GitHub Actions and GitLab CI. 4 | 5 | # Usage 6 | 7 | ``` 8 | import {generateProvenance} from '@npmcli/generate-provenance' 9 | 10 | const provenanceStatement = generateProvenance({ 11 | subject: [{ 12 | name: '', 13 | digest: { 14 | alg: '' 15 | } 16 | }], 17 | }) 18 | ``` 19 | -------------------------------------------------------------------------------- /packages/generate-provenance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@npmcli/generate-provenance", 3 | "version": "0.0.1", 4 | "description": "Generate SLSA provenance", 5 | "author": "Philip Harrison @feelepxyz", 6 | "license": "Artistic-2.0", 7 | "keywords": [ 8 | "npm", 9 | "provenance" 10 | ], 11 | "main": "dist/index.js", 12 | "types": "dist/index.d.ts", 13 | "files": [ 14 | "/dist" 15 | ], 16 | "publishConfig": { 17 | "access": "public", 18 | "provenance": true 19 | }, 20 | "scripts": { 21 | "clean": "shx rm -rf dist", 22 | "prebuild": "npm run clean", 23 | "build": "tsc -b", 24 | "prepack": "npm run build" 25 | }, 26 | "dependencies": { 27 | "ci-info": "^3.9.0" 28 | }, 29 | "devDependencies": { 30 | "@types/debug": "^4.1.10", 31 | "tslib": "^2.6.1" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/npm/provenance.git" 36 | }, 37 | "homepage": "https://github.com/npm/provenance/tree/main/packages/generate-provenance#readme", 38 | "bugs": "https://github.com/npm/provenance/issues" 39 | } -------------------------------------------------------------------------------- /packages/generate-provenance/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as ci from 'ci-info'; 2 | 3 | type Subject = { 4 | name: string; 5 | digest: Record; 6 | }; 7 | 8 | const INTOTO_STATEMENT_V01_TYPE = 'https://in-toto.io/Statement/v0.1'; 9 | const INTOTO_STATEMENT_V1_TYPE = 'https://in-toto.io/Statement/v1'; 10 | const SLSA_PREDICATE_V02_TYPE = 'https://slsa.dev/provenance/v0.2'; 11 | const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'; 12 | 13 | const GITHUB_BUILDER_ID_PREFIX = 'https://github.com/actions/runner'; 14 | const GITHUB_BUILD_TYPE = 15 | 'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1'; 16 | 17 | const GITLAB_BUILD_TYPE_PREFIX = 'https://github.com/npm/cli/gitlab'; 18 | const GITLAB_BUILD_TYPE_VERSION = 'v0alpha1'; 19 | 20 | export const generateProvenance = ( 21 | subject: Subject, 22 | env: NodeJS.ProcessEnv 23 | ): object | void => { 24 | let payload; 25 | if (ci.GITHUB_ACTIONS) { 26 | /* istanbul ignore next - not covering missing env var case */ 27 | const [workflowPath, workflowRef] = (env.GITHUB_WORKFLOW_REF || '') 28 | .replace(env.GITHUB_REPOSITORY + '/', '') 29 | .split('@'); 30 | payload = { 31 | _type: INTOTO_STATEMENT_V1_TYPE, 32 | subject, 33 | predicateType: SLSA_PREDICATE_V1_TYPE, 34 | predicate: { 35 | buildDefinition: { 36 | buildType: GITHUB_BUILD_TYPE, 37 | externalParameters: { 38 | workflow: { 39 | ref: workflowRef, 40 | repository: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`, 41 | path: workflowPath, 42 | }, 43 | }, 44 | internalParameters: { 45 | github: { 46 | event_name: env.GITHUB_EVENT_NAME, 47 | repository_id: env.GITHUB_REPOSITORY_ID, 48 | repository_owner_id: env.GITHUB_REPOSITORY_OWNER_ID, 49 | }, 50 | }, 51 | resolvedDependencies: [ 52 | { 53 | uri: `git+${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}@${env.GITHUB_REF}`, 54 | digest: { 55 | gitCommit: env.GITHUB_SHA, 56 | }, 57 | }, 58 | ], 59 | }, 60 | runDetails: { 61 | builder: { 62 | id: `${GITHUB_BUILDER_ID_PREFIX}/${env.RUNNER_ENVIRONMENT}`, 63 | }, 64 | metadata: { 65 | /* eslint-disable-next-line max-len */ 66 | invocationId: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}/attempts/${env.GITHUB_RUN_ATTEMPT}`, 67 | }, 68 | }, 69 | }, 70 | }; 71 | } else if (ci.GITLAB) { 72 | payload = { 73 | _type: INTOTO_STATEMENT_V01_TYPE, 74 | subject, 75 | predicateType: SLSA_PREDICATE_V02_TYPE, 76 | predicate: { 77 | buildType: `${GITLAB_BUILD_TYPE_PREFIX}/${GITLAB_BUILD_TYPE_VERSION}`, 78 | builder: { id: `${env.CI_PROJECT_URL}/-/runners/${env.CI_RUNNER_ID}` }, 79 | invocation: { 80 | configSource: { 81 | uri: `git+${env.CI_PROJECT_URL}`, 82 | digest: { 83 | sha1: env.CI_COMMIT_SHA, 84 | }, 85 | entryPoint: env.CI_JOB_NAME, 86 | }, 87 | parameters: { 88 | CI: env.CI, 89 | CI_API_GRAPHQL_URL: env.CI_API_GRAPHQL_URL, 90 | CI_API_V4_URL: env.CI_API_V4_URL, 91 | CI_BUILD_BEFORE_SHA: env.CI_BUILD_BEFORE_SHA, 92 | CI_BUILD_ID: env.CI_BUILD_ID, 93 | CI_BUILD_NAME: env.CI_BUILD_NAME, 94 | CI_BUILD_REF: env.CI_BUILD_REF, 95 | CI_BUILD_REF_NAME: env.CI_BUILD_REF_NAME, 96 | CI_BUILD_REF_SLUG: env.CI_BUILD_REF_SLUG, 97 | CI_BUILD_STAGE: env.CI_BUILD_STAGE, 98 | CI_COMMIT_BEFORE_SHA: env.CI_COMMIT_BEFORE_SHA, 99 | CI_COMMIT_BRANCH: env.CI_COMMIT_BRANCH, 100 | CI_COMMIT_REF_NAME: env.CI_COMMIT_REF_NAME, 101 | CI_COMMIT_REF_PROTECTED: env.CI_COMMIT_REF_PROTECTED, 102 | CI_COMMIT_REF_SLUG: env.CI_COMMIT_REF_SLUG, 103 | CI_COMMIT_SHA: env.CI_COMMIT_SHA, 104 | CI_COMMIT_SHORT_SHA: env.CI_COMMIT_SHORT_SHA, 105 | CI_COMMIT_TIMESTAMP: env.CI_COMMIT_TIMESTAMP, 106 | CI_COMMIT_TITLE: env.CI_COMMIT_TITLE, 107 | CI_CONFIG_PATH: env.CI_CONFIG_PATH, 108 | CI_DEFAULT_BRANCH: env.CI_DEFAULT_BRANCH, 109 | CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX: 110 | env.CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX, 111 | CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX: 112 | env.CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX, 113 | CI_DEPENDENCY_PROXY_SERVER: env.CI_DEPENDENCY_PROXY_SERVER, 114 | CI_DEPENDENCY_PROXY_USER: env.CI_DEPENDENCY_PROXY_USER, 115 | CI_JOB_ID: env.CI_JOB_ID, 116 | CI_JOB_NAME: env.CI_JOB_NAME, 117 | CI_JOB_NAME_SLUG: env.CI_JOB_NAME_SLUG, 118 | CI_JOB_STAGE: env.CI_JOB_STAGE, 119 | CI_JOB_STARTED_AT: env.CI_JOB_STARTED_AT, 120 | CI_JOB_URL: env.CI_JOB_URL, 121 | CI_NODE_TOTAL: env.CI_NODE_TOTAL, 122 | CI_PAGES_DOMAIN: env.CI_PAGES_DOMAIN, 123 | CI_PAGES_URL: env.CI_PAGES_URL, 124 | CI_PIPELINE_CREATED_AT: env.CI_PIPELINE_CREATED_AT, 125 | CI_PIPELINE_ID: env.CI_PIPELINE_ID, 126 | CI_PIPELINE_IID: env.CI_PIPELINE_IID, 127 | CI_PIPELINE_SOURCE: env.CI_PIPELINE_SOURCE, 128 | CI_PIPELINE_URL: env.CI_PIPELINE_URL, 129 | CI_PROJECT_CLASSIFICATION_LABEL: 130 | env.CI_PROJECT_CLASSIFICATION_LABEL, 131 | CI_PROJECT_DESCRIPTION: env.CI_PROJECT_DESCRIPTION, 132 | CI_PROJECT_ID: env.CI_PROJECT_ID, 133 | CI_PROJECT_NAME: env.CI_PROJECT_NAME, 134 | CI_PROJECT_NAMESPACE: env.CI_PROJECT_NAMESPACE, 135 | CI_PROJECT_NAMESPACE_ID: env.CI_PROJECT_NAMESPACE_ID, 136 | CI_PROJECT_PATH: env.CI_PROJECT_PATH, 137 | CI_PROJECT_PATH_SLUG: env.CI_PROJECT_PATH_SLUG, 138 | CI_PROJECT_REPOSITORY_LANGUAGES: 139 | env.CI_PROJECT_REPOSITORY_LANGUAGES, 140 | CI_PROJECT_ROOT_NAMESPACE: env.CI_PROJECT_ROOT_NAMESPACE, 141 | CI_PROJECT_TITLE: env.CI_PROJECT_TITLE, 142 | CI_PROJECT_URL: env.CI_PROJECT_URL, 143 | CI_PROJECT_VISIBILITY: env.CI_PROJECT_VISIBILITY, 144 | CI_REGISTRY: env.CI_REGISTRY, 145 | CI_REGISTRY_IMAGE: env.CI_REGISTRY_IMAGE, 146 | CI_REGISTRY_USER: env.CI_REGISTRY_USER, 147 | CI_RUNNER_DESCRIPTION: env.CI_RUNNER_DESCRIPTION, 148 | CI_RUNNER_ID: env.CI_RUNNER_ID, 149 | CI_RUNNER_TAGS: env.CI_RUNNER_TAGS, 150 | CI_SERVER_HOST: env.CI_SERVER_HOST, 151 | CI_SERVER_NAME: env.CI_SERVER_NAME, 152 | CI_SERVER_PORT: env.CI_SERVER_PORT, 153 | CI_SERVER_PROTOCOL: env.CI_SERVER_PROTOCOL, 154 | CI_SERVER_REVISION: env.CI_SERVER_REVISION, 155 | CI_SERVER_SHELL_SSH_HOST: env.CI_SERVER_SHELL_SSH_HOST, 156 | CI_SERVER_SHELL_SSH_PORT: env.CI_SERVER_SHELL_SSH_PORT, 157 | CI_SERVER_URL: env.CI_SERVER_URL, 158 | CI_SERVER_VERSION: env.CI_SERVER_VERSION, 159 | CI_SERVER_VERSION_MAJOR: env.CI_SERVER_VERSION_MAJOR, 160 | CI_SERVER_VERSION_MINOR: env.CI_SERVER_VERSION_MINOR, 161 | CI_SERVER_VERSION_PATCH: env.CI_SERVER_VERSION_PATCH, 162 | CI_TEMPLATE_REGISTRY_HOST: env.CI_TEMPLATE_REGISTRY_HOST, 163 | GITLAB_CI: env.GITLAB_CI, 164 | GITLAB_FEATURES: env.GITLAB_FEATURES, 165 | GITLAB_USER_ID: env.GITLAB_USER_ID, 166 | GITLAB_USER_LOGIN: env.GITLAB_USER_LOGIN, 167 | RUNNER_GENERATE_ARTIFACTS_METADATA: 168 | env.RUNNER_GENERATE_ARTIFACTS_METADATA, 169 | }, 170 | environment: { 171 | name: env.CI_RUNNER_DESCRIPTION, 172 | architecture: env.CI_RUNNER_EXECUTABLE_ARCH, 173 | server: env.CI_SERVER_URL, 174 | project: env.CI_PROJECT_PATH, 175 | job: { 176 | id: env.CI_JOB_ID, 177 | }, 178 | pipeline: { 179 | id: env.CI_PIPELINE_ID, 180 | ref: env.CI_CONFIG_PATH, 181 | }, 182 | }, 183 | }, 184 | metadata: { 185 | buildInvocationId: `${env.CI_JOB_URL}`, 186 | completeness: { 187 | parameters: true, 188 | environment: true, 189 | materials: false, 190 | }, 191 | reproducible: false, 192 | }, 193 | materials: [ 194 | { 195 | uri: `git+${env.CI_PROJECT_URL}`, 196 | digest: { 197 | sha1: env.CI_COMMIT_SHA, 198 | }, 199 | }, 200 | ], 201 | }, 202 | }; 203 | } else { 204 | new Error( 205 | 'Automatic provenance generation not supported for provider: ' + ci.name 206 | ); 207 | } 208 | return payload; 209 | }; 210 | -------------------------------------------------------------------------------- /packages/generate-provenance/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "composite": true 7 | }, 8 | "exclude": ["./dist"], 9 | } 10 | -------------------------------------------------------------------------------- /packages/verify-provenance/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @npmcli/verify-provenance 2 | -------------------------------------------------------------------------------- /packages/verify-provenance/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/verify-provenance/README.md: -------------------------------------------------------------------------------- 1 | # @npmcli/verify-provenance 2 | 3 | Library for verifying SLSA provenance from GitHub Actions and GitLab CI. 4 | 5 | # Usage 6 | 7 | ``` 8 | import {verifyProvenance} from '@npmcli/generate-provenance' 9 | 10 | // TODO 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/verify-provenance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@npmcli/verify-provenance", 3 | "version": "0.0.0", 4 | "description": "Verify SLSA provenance", 5 | "author": "Philip Harrison @feelepxyz", 6 | "license": "Artistic-2.0", 7 | "keywords": [ 8 | "npm", 9 | "provenance" 10 | ], 11 | "main": "dist/index.js", 12 | "types": "dist/index.d.ts", 13 | "files": [ 14 | "/dist" 15 | ], 16 | "publishConfig": { 17 | "access": "public", 18 | "provenance": true 19 | }, 20 | "scripts": { 21 | "clean": "shx rm -rf dist", 22 | "prebuild": "npm run clean", 23 | "build": "tsc -b", 24 | "prepack": "npm run build" 25 | }, 26 | "dependencies": {}, 27 | "devDependencies": { 28 | "@types/debug": "^4.1.10", 29 | "tslib": "^2.6.1" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/npm/provenance.git" 34 | }, 35 | "homepage": "https://github.com/npm/provenance/tree/main/packages/verify-provenance#readme", 36 | "bugs": "https://github.com/npm/provenance/issues" 37 | } -------------------------------------------------------------------------------- /packages/verify-provenance/src/index.ts: -------------------------------------------------------------------------------- 1 | // TODO 2 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 3 | export const verifyProvenance = (bundle: object): boolean => { 4 | return false; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/verify-provenance/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | }, 7 | "exclude": ["./dist"], 8 | } 9 | -------------------------------------------------------------------------------- /provenance-statement.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": 1 3 | } -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "noFallthroughCasesInSwitch": true, 6 | "noImplicitOverride": true, 7 | "allowUnreachableCode": false, 8 | "noImplicitReturns": true, 9 | "noUnusedParameters": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "packages/cli" }, 5 | { "path": "packages/generate-provenance" }, 6 | ] 7 | } 8 | --------------------------------------------------------------------------------