Skip to content

Commit 00bbc68

Browse files
committed
feat: add --provenance flag to yarn npm publish
1 parent e06bacd commit 00bbc68

35 files changed

+776
-155
lines changed

.pnp.cjs

+260-103
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

.yarn/versions/e30c5e10.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
releases:
2+
"@yarnpkg/cli": minor
3+
"@yarnpkg/core": minor
4+
"@yarnpkg/plugin-npm": minor
5+
"@yarnpkg/plugin-npm-cli": minor
6+
7+
declined:
8+
- "@yarnpkg/plugin-compat"
9+
- "@yarnpkg/plugin-constraints"
10+
- "@yarnpkg/plugin-dlx"
11+
- "@yarnpkg/plugin-essentials"
12+
- "@yarnpkg/plugin-exec"
13+
- "@yarnpkg/plugin-file"
14+
- "@yarnpkg/plugin-git"
15+
- "@yarnpkg/plugin-github"
16+
- "@yarnpkg/plugin-http"
17+
- "@yarnpkg/plugin-init"
18+
- "@yarnpkg/plugin-interactive-tools"
19+
- "@yarnpkg/plugin-link"
20+
- "@yarnpkg/plugin-nm"
21+
- "@yarnpkg/plugin-pack"
22+
- "@yarnpkg/plugin-patch"
23+
- "@yarnpkg/plugin-pnp"
24+
- "@yarnpkg/plugin-pnpm"
25+
- "@yarnpkg/plugin-stage"
26+
- "@yarnpkg/plugin-typescript"
27+
- "@yarnpkg/plugin-version"
28+
- "@yarnpkg/plugin-workspace-tools"
29+
- "@yarnpkg/builder"
30+
- "@yarnpkg/doctor"
31+
- "@yarnpkg/extensions"
32+
- "@yarnpkg/nm"
33+
- "@yarnpkg/pnpify"
34+
- "@yarnpkg/sdks"

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
},
2929
"resolutions": {
3030
"ink@^3.2.0": "patch:ink@npm%3A3.2.0#~/.yarn/patches/ink-npm-3.2.0-2f1df5b094.patch",
31-
"yoga-layout-prebuilt": "patch:[email protected]#./.yarn/patches/yoga-layout-prebuilt.patch"
31+
"yoga-layout-prebuilt": "patch:[email protected]#./.yarn/patches/yoga-layout-prebuilt.patch",
32+
"make-fetch-happen@npm:^14.0.1": "portal:packages/make-fetch-smaller",
33+
"make-fetch-happen@npm:^14.0.2": "portal:packages/make-fetch-smaller"
3234
},
3335
"dependenciesMeta": {
3436
"core-js": {

packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx

+41
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,44 @@ Our research showed that even our power users aren't always aware of some of the
444444
When enabled, the `enableOfflineMode` flag tells Yarn to ignore remote registries and only pull data from its internal caches. This is a handy mode when working from within network-constrained environments such as planes or trains.
445445

446446
To leave the offline work mode, check how it got enabled by running `yarn config --why`. If `<environment>`, run `unset YARN_ENABLE_OFFLINE_MODE` in your terminal. Otherwise, remove the `enableOfflineMode` flag from the relevant `.yarnrc.yml` files.
447+
448+
## YN0091 - `INVALID_PROVENANCE_ENVIRONMENT`
449+
450+
This error is triggered when the [provenance statement](https://docs.npmjs.com/generating-provenance-statements) cannot be generated in the current environment. GitHub Actions and GitLab CI are the only supported environments at the moment, and this error is triggered when either running in another environment or when credentials are missing.
451+
452+
On GitHub Actions, you need to grant the `write-id` permission to your workflow. Here is an example of how to do that:
453+
454+
```yaml
455+
name: Publish Package to npmjs
456+
on:
457+
push:
458+
branches: [main]
459+
jobs:
460+
publish:
461+
runs-on: ubuntu-latest # Must run on GitHub-hosted runners
462+
permissions:
463+
id-token: write
464+
steps:
465+
- uses: actions/checkout@v4
466+
- run: npm install -g corepack && corepack enable
467+
- run: yarn && yarn build
468+
- run: yarn config set npmAuthToken '${{ secrets.NPM_TOKEN }}'
469+
- run: yarn publish --provenance --tolerate-republish
470+
```
471+
472+
On GitLab CI, you need to produce a `SIGSTORE_ID_TOKEN` for your workflow. Here is an example of how to do that:
473+
474+
```yaml
475+
publish:
476+
image: 'node:22'
477+
rules:
478+
- if: '$CI_COMMIT_BRANCH == "main"'
479+
id_tokens:
480+
SIGSTORE_ID_TOKEN:
481+
aud: sigstore
482+
script:
483+
- npm install -g corepack && corepack enable
484+
- yarn && yarn build
485+
- yarn config set npmAuthToken $NPM_TOKEN
486+
- yarn publish --provenance --tolerate-republish
487+
```

packages/make-fetch-smaller/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# make-fetch-smaller
2+
3+
This package is a drop-in replacement for `make-fetch-happen`, but uses Node.js native `fetch` instead of pulling [79 dependencies.](https://node-modules.dev/graph#install=make-fetch-happen)
4+
5+
It is used by the [sigstore](https://www.npmjs.com/package/sigstore) package and its dependencies to produce the provenance statement for packages published to the npm registry with `yarn npm publish --provenance`.

packages/make-fetch-smaller/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// eslint-disable-next-line
2+
module.exports = fetch;
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "make-fetch-smaller",
3+
"private": true,
4+
"repository": {
5+
"type": "git",
6+
"url": "git+https://github.com/yarnpkg/berry.git",
7+
"directory": "packages/make-fetch-smaller"
8+
},
9+
"license": "BSD-2-Clause",
10+
"type": "commonjs",
11+
"engines": {
12+
"node": ">=18.12.0"
13+
}
14+
}

packages/plugin-npm-cli/sources/commands/npm/publish.ts

+5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ export default class NpmPublishCommand extends BaseCommand {
4242
description: `The OTP token to use with the command`,
4343
});
4444

45+
provenance = Option.Boolean(`--provenance`, false, {
46+
description: `Generate provenance for the package. Only available in GitHub Actions and GitLab CI.`,
47+
});
48+
4549
async execute() {
4650
const configuration = await Configuration.find(this.context.cwd, this.context.plugins);
4751
const {project, workspace} = await Project.find(configuration, this.context.cwd);
@@ -107,6 +111,7 @@ export default class NpmPublishCommand extends BaseCommand {
107111
tag: this.tag,
108112
registry,
109113
gitHead,
114+
provenance: this.provenance,
110115
});
111116

112117
await npmHttpUtils.put(npmHttpUtils.getIdentUrl(ident), body, {

packages/plugin-npm/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ This plugin adds support for downloading packages from the npm registry.
55
## Install
66

77
This plugin is included by default in Yarn.
8+
9+
## Attribution
10+
11+
Provenance code adapted from [npm/cli](https://github.com/npm/cli/blob/04f53ce13201b460123067d7153f1681342548e1/workspaces/libnpmpublish/lib/provenance.js), under [ISC license](https://github.com/npm/cli/blob/04f53ce13201b460123067d7153f1681342548e1/workspaces/libnpmpublish/LICENSE).

packages/plugin-npm/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"enquirer": "^2.3.6",
1313
"lodash": "^4.17.15",
1414
"semver": "^7.1.2",
15-
"ssri": "^6.0.1",
15+
"sigstore": "^3.1.0",
16+
"ssri": "^12.0.0",
1617
"tslib": "^2.4.0"
1718
},
1819
"peerDependencies": {
@@ -22,7 +23,7 @@
2223
"devDependencies": {
2324
"@types/lodash": "^4.14.136",
2425
"@types/semver": "^7.1.0",
25-
"@types/ssri": "^6.0.1",
26+
"@types/ssri": "^7.1.5",
2627
"@yarnpkg/core": "workspace:^",
2728
"@yarnpkg/plugin-pack": "workspace:^"
2829
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/**
2+
* This code is adapted from the npm project, under ISC License.
3+
*
4+
* Original source:
5+
* https://github.com/npm/cli/blob/04f53ce13201b460123067d7153f1681342548e1/workspaces/libnpmpublish/lib/provenance.js
6+
*/
7+
8+
import {MessageName, ReportError} from '@yarnpkg/core';
9+
import * as sigstore from 'sigstore';
10+
11+
const {env} = process;
12+
13+
const INTOTO_PAYLOAD_TYPE = `application/vnd.in-toto+json`;
14+
const INTOTO_STATEMENT_V01_TYPE = `https://in-toto.io/Statement/v0.1`;
15+
const INTOTO_STATEMENT_V1_TYPE = `https://in-toto.io/Statement/v1`;
16+
const SLSA_PREDICATE_V02_TYPE = `https://slsa.dev/provenance/v0.2`;
17+
const SLSA_PREDICATE_V1_TYPE = `https://slsa.dev/provenance/v1`;
18+
19+
const GITHUB_BUILDER_ID_PREFIX = `https://github.com/actions/runner`;
20+
const GITHUB_BUILD_TYPE = `https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1`;
21+
22+
const GITLAB_BUILD_TYPE_PREFIX = `https://github.com/npm/cli/gitlab`;
23+
const GITLAB_BUILD_TYPE_VERSION = `v0alpha1`;
24+
25+
export const generateProvenance = async (subject: any, opts?: sigstore.SignOptions) => {
26+
let payload: unknown;
27+
if (env.GITHUB_ACTIONS) {
28+
if (!env.ACTIONS_ID_TOKEN_REQUEST_URL) {
29+
throw new ReportError(
30+
MessageName.INVALID_PROVENANCE_ENVIRONMENT,
31+
`Provenance generation in GitHub Actions requires "write" access to the "id-token" permission`,
32+
);
33+
}
34+
35+
const relativeRef = (env.GITHUB_WORKFLOW_REF || ``).replace(`${env.GITHUB_REPOSITORY}/`, ``);
36+
const delimiterIndex = relativeRef.indexOf(`@`);
37+
const workflowPath = relativeRef.slice(0, delimiterIndex);
38+
const workflowRef = relativeRef.slice(delimiterIndex + 1);
39+
40+
payload = {
41+
_type: INTOTO_STATEMENT_V1_TYPE,
42+
subject,
43+
predicateType: SLSA_PREDICATE_V1_TYPE,
44+
predicate: {
45+
buildDefinition: {
46+
buildType: GITHUB_BUILD_TYPE,
47+
externalParameters: {
48+
workflow: {
49+
ref: workflowRef,
50+
repository: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`,
51+
path: workflowPath,
52+
},
53+
},
54+
internalParameters: {
55+
github: {
56+
event_name: env.GITHUB_EVENT_NAME,
57+
repository_id: env.GITHUB_REPOSITORY_ID,
58+
repository_owner_id: env.GITHUB_REPOSITORY_OWNER_ID,
59+
},
60+
},
61+
resolvedDependencies: [
62+
{
63+
uri: `git+${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}@${env.GITHUB_REF}`,
64+
digest: {
65+
gitCommit: env.GITHUB_SHA,
66+
},
67+
},
68+
],
69+
},
70+
runDetails: {
71+
builder: {id: `${GITHUB_BUILDER_ID_PREFIX}/${env.RUNNER_ENVIRONMENT}`},
72+
metadata: {
73+
invocationId: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}/attempts/${env.GITHUB_RUN_ATTEMPT}`,
74+
},
75+
},
76+
},
77+
};
78+
} else if (env.GITLAB_CI) {
79+
if (!env.SIGSTORE_ID_TOKEN) {
80+
throw new ReportError(
81+
MessageName.INVALID_PROVENANCE_ENVIRONMENT,
82+
`Provenance generation in GitLab CI requires "SIGSTORE_ID_TOKEN" with "sigstore" audience to be present in "id_tokens". For more info see:\nhttps://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html`,
83+
);
84+
}
85+
86+
payload = {
87+
_type: INTOTO_STATEMENT_V01_TYPE,
88+
subject,
89+
predicateType: SLSA_PREDICATE_V02_TYPE,
90+
predicate: {
91+
buildType: `${GITLAB_BUILD_TYPE_PREFIX}/${GITLAB_BUILD_TYPE_VERSION}`,
92+
builder: {id: `${env.CI_PROJECT_URL}/-/runners/${env.CI_RUNNER_ID}`},
93+
invocation: {
94+
configSource: {
95+
uri: `git+${env.CI_PROJECT_URL}`,
96+
digest: {
97+
sha1: env.CI_COMMIT_SHA,
98+
},
99+
entryPoint: env.CI_JOB_NAME,
100+
},
101+
parameters: {
102+
CI: env.CI,
103+
CI_API_GRAPHQL_URL: env.CI_API_GRAPHQL_URL,
104+
CI_API_V4_URL: env.CI_API_V4_URL,
105+
CI_BUILD_BEFORE_SHA: env.CI_BUILD_BEFORE_SHA,
106+
CI_BUILD_ID: env.CI_BUILD_ID,
107+
CI_BUILD_NAME: env.CI_BUILD_NAME,
108+
CI_BUILD_REF: env.CI_BUILD_REF,
109+
CI_BUILD_REF_NAME: env.CI_BUILD_REF_NAME,
110+
CI_BUILD_REF_SLUG: env.CI_BUILD_REF_SLUG,
111+
CI_BUILD_STAGE: env.CI_BUILD_STAGE,
112+
CI_COMMIT_BEFORE_SHA: env.CI_COMMIT_BEFORE_SHA,
113+
CI_COMMIT_BRANCH: env.CI_COMMIT_BRANCH,
114+
CI_COMMIT_REF_NAME: env.CI_COMMIT_REF_NAME,
115+
CI_COMMIT_REF_PROTECTED: env.CI_COMMIT_REF_PROTECTED,
116+
CI_COMMIT_REF_SLUG: env.CI_COMMIT_REF_SLUG,
117+
CI_COMMIT_SHA: env.CI_COMMIT_SHA,
118+
CI_COMMIT_SHORT_SHA: env.CI_COMMIT_SHORT_SHA,
119+
CI_COMMIT_TIMESTAMP: env.CI_COMMIT_TIMESTAMP,
120+
CI_COMMIT_TITLE: env.CI_COMMIT_TITLE,
121+
CI_CONFIG_PATH: env.CI_CONFIG_PATH,
122+
CI_DEFAULT_BRANCH: env.CI_DEFAULT_BRANCH,
123+
CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX:
124+
env.CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX,
125+
CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX: env.CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX,
126+
CI_DEPENDENCY_PROXY_SERVER: env.CI_DEPENDENCY_PROXY_SERVER,
127+
CI_DEPENDENCY_PROXY_USER: env.CI_DEPENDENCY_PROXY_USER,
128+
CI_JOB_ID: env.CI_JOB_ID,
129+
CI_JOB_NAME: env.CI_JOB_NAME,
130+
CI_JOB_NAME_SLUG: env.CI_JOB_NAME_SLUG,
131+
CI_JOB_STAGE: env.CI_JOB_STAGE,
132+
CI_JOB_STARTED_AT: env.CI_JOB_STARTED_AT,
133+
CI_JOB_URL: env.CI_JOB_URL,
134+
CI_NODE_TOTAL: env.CI_NODE_TOTAL,
135+
CI_PAGES_DOMAIN: env.CI_PAGES_DOMAIN,
136+
CI_PAGES_URL: env.CI_PAGES_URL,
137+
CI_PIPELINE_CREATED_AT: env.CI_PIPELINE_CREATED_AT,
138+
CI_PIPELINE_ID: env.CI_PIPELINE_ID,
139+
CI_PIPELINE_IID: env.CI_PIPELINE_IID,
140+
CI_PIPELINE_SOURCE: env.CI_PIPELINE_SOURCE,
141+
CI_PIPELINE_URL: env.CI_PIPELINE_URL,
142+
CI_PROJECT_CLASSIFICATION_LABEL: env.CI_PROJECT_CLASSIFICATION_LABEL,
143+
CI_PROJECT_DESCRIPTION: env.CI_PROJECT_DESCRIPTION,
144+
CI_PROJECT_ID: env.CI_PROJECT_ID,
145+
CI_PROJECT_NAME: env.CI_PROJECT_NAME,
146+
CI_PROJECT_NAMESPACE: env.CI_PROJECT_NAMESPACE,
147+
CI_PROJECT_NAMESPACE_ID: env.CI_PROJECT_NAMESPACE_ID,
148+
CI_PROJECT_PATH: env.CI_PROJECT_PATH,
149+
CI_PROJECT_PATH_SLUG: env.CI_PROJECT_PATH_SLUG,
150+
CI_PROJECT_REPOSITORY_LANGUAGES: env.CI_PROJECT_REPOSITORY_LANGUAGES,
151+
CI_PROJECT_ROOT_NAMESPACE: env.CI_PROJECT_ROOT_NAMESPACE,
152+
CI_PROJECT_TITLE: env.CI_PROJECT_TITLE,
153+
CI_PROJECT_URL: env.CI_PROJECT_URL,
154+
CI_PROJECT_VISIBILITY: env.CI_PROJECT_VISIBILITY,
155+
CI_REGISTRY: env.CI_REGISTRY,
156+
CI_REGISTRY_IMAGE: env.CI_REGISTRY_IMAGE,
157+
CI_REGISTRY_USER: env.CI_REGISTRY_USER,
158+
CI_RUNNER_DESCRIPTION: env.CI_RUNNER_DESCRIPTION,
159+
CI_RUNNER_ID: env.CI_RUNNER_ID,
160+
CI_RUNNER_TAGS: env.CI_RUNNER_TAGS,
161+
CI_SERVER_HOST: env.CI_SERVER_HOST,
162+
CI_SERVER_NAME: env.CI_SERVER_NAME,
163+
CI_SERVER_PORT: env.CI_SERVER_PORT,
164+
CI_SERVER_PROTOCOL: env.CI_SERVER_PROTOCOL,
165+
CI_SERVER_REVISION: env.CI_SERVER_REVISION,
166+
CI_SERVER_SHELL_SSH_HOST: env.CI_SERVER_SHELL_SSH_HOST,
167+
CI_SERVER_SHELL_SSH_PORT: env.CI_SERVER_SHELL_SSH_PORT,
168+
CI_SERVER_URL: env.CI_SERVER_URL,
169+
CI_SERVER_VERSION: env.CI_SERVER_VERSION,
170+
CI_SERVER_VERSION_MAJOR: env.CI_SERVER_VERSION_MAJOR,
171+
CI_SERVER_VERSION_MINOR: env.CI_SERVER_VERSION_MINOR,
172+
CI_SERVER_VERSION_PATCH: env.CI_SERVER_VERSION_PATCH,
173+
CI_TEMPLATE_REGISTRY_HOST: env.CI_TEMPLATE_REGISTRY_HOST,
174+
GITLAB_CI: env.GITLAB_CI,
175+
GITLAB_FEATURES: env.GITLAB_FEATURES,
176+
GITLAB_USER_ID: env.GITLAB_USER_ID,
177+
GITLAB_USER_LOGIN: env.GITLAB_USER_LOGIN,
178+
RUNNER_GENERATE_ARTIFACTS_METADATA: env.RUNNER_GENERATE_ARTIFACTS_METADATA,
179+
},
180+
environment: {
181+
name: env.CI_RUNNER_DESCRIPTION,
182+
architecture: env.CI_RUNNER_EXECUTABLE_ARCH,
183+
server: env.CI_SERVER_URL,
184+
project: env.CI_PROJECT_PATH,
185+
job: {
186+
id: env.CI_JOB_ID,
187+
},
188+
pipeline: {
189+
id: env.CI_PIPELINE_ID,
190+
ref: env.CI_CONFIG_PATH,
191+
},
192+
},
193+
},
194+
metadata: {
195+
buildInvocationId: `${env.CI_JOB_URL}`,
196+
completeness: {
197+
parameters: true,
198+
environment: true,
199+
materials: false,
200+
},
201+
reproducible: false,
202+
},
203+
materials: [
204+
{
205+
uri: `git+${env.CI_PROJECT_URL}`,
206+
digest: {
207+
sha1: env.CI_COMMIT_SHA,
208+
},
209+
},
210+
],
211+
},
212+
};
213+
} else {
214+
throw new ReportError(
215+
MessageName.INVALID_PROVENANCE_ENVIRONMENT,
216+
`Provenance generation is only supported in GitHub Actions and GitLab CI`,
217+
);
218+
}
219+
return sigstore.attest(Buffer.from(JSON.stringify(payload)), INTOTO_PAYLOAD_TYPE, opts);
220+
};

0 commit comments

Comments
 (0)