Get NPM signing keys from @sigstore/tuf

Instead of hardcoding NPM signing keys for verification we get them from
sigstore’s TUF repository. This is in line with how npm implements
signature verification.

Fixes #616, fixes #612
This commit is contained in:
Thomas Scholtes 2025-04-04 16:16:45 +02:00
parent 679bcefda5
commit aa1b84939a
No known key found for this signature in database
GPG Key ID: 7E113D26B1FDDDED
12 changed files with 338 additions and 60 deletions

View File

@ -0,0 +1,31 @@
diff --git a/dist/fetcher.js b/dist/fetcher.js
index f966ce1bb0cdc6c785ce1263f1faea15d3fe764c..111588c64ba0cc049cabeb471d39f0fdc78bbb7e 100644
--- a/dist/fetcher.js
+++ b/dist/fetcher.js
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultFetcher = exports.BaseFetcher = void 0;
const debug_1 = __importDefault(require("debug"));
const fs_1 = __importDefault(require("fs"));
-const make_fetch_happen_1 = __importDefault(require("make-fetch-happen"));
+const stream = require("node:stream")
const util_1 = __importDefault(require("util"));
const error_1 = require("./error");
const tmpfile_1 = require("./utils/tmpfile");
@@ -61,14 +61,12 @@ class DefaultFetcher extends BaseFetcher {
}
async fetch(url) {
log('GET %s', url);
- const response = await (0, make_fetch_happen_1.default)(url, {
- timeout: this.timeout,
- retry: this.retry,
- });
+ const response = await globalThis.tufJsFetch(url);
+
if (!response.ok || !response?.body) {
throw new error_1.DownloadHTTPError('Failed to download', response.status);
}
- return response.body;
+ return stream.Readable.fromWeb(response.body);
}
}
exports.DefaultFetcher = DefaultFetcher;

View File

@ -355,8 +355,17 @@ same major line. Should you need to upgrade to a new major, use an explicit
[`proxy-from-env`](https://github.com/Rob--W/proxy-from-env). [`proxy-from-env`](https://github.com/Rob--W/proxy-from-env).
- `COREPACK_INTEGRITY_KEYS` can be set to an empty string or `0` to - `COREPACK_INTEGRITY_KEYS` can be set to an empty string or `0` to
instruct Corepack to skip integrity checks, or to a JSON string containing instruct Corepack to skip signature verification, or to a JSON string
custom keys. containing custom keys. The format based on the response of the
`GET /-/npm/v1/keys` endpoint of npm registry under the `npm` key. That is,
```bash
curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}'
```
See the [npm documentation on
signatures](https://docs.npmjs.com/about-registry-signatures) for more
information.
## Troubleshooting ## Troubleshooting

View File

@ -18,6 +18,8 @@
"license": "MIT", "license": "MIT",
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728", "packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728",
"devDependencies": { "devDependencies": {
"@sigstore/tuf": "^3.1.0",
"@sinonjs/fake-timers": "^14.0.0",
"@types/debug": "^4.1.5", "@types/debug": "^4.1.5",
"@types/node": "^20.4.6", "@types/node": "^20.4.6",
"@types/proxy-from-env": "^1", "@types/proxy-from-env": "^1",
@ -43,7 +45,8 @@
"which": "^5.0.0" "which": "^5.0.0"
}, },
"resolutions": { "resolutions": {
"undici-types": "6.x" "undici-types": "6.x",
"tuf-js@npm:^3.0.1": "patch:tuf-js@npm%3A3.0.1#~/.yarn/patches/tuf-js-npm-3.0.1-9135d15fbd.patch"
}, },
"scripts": { "scripts": {
"build": "run clean && run build:bundle && tsx ./mkshims.ts", "build": "run clean && run build:bundle && tsx ./mkshims.ts",

View File

@ -289,7 +289,7 @@ export async function installVersion(installTarget: string, locator: Locator, {s
if (signatures! == null || integrity! == null) if (signatures! == null || integrity! == null)
({signatures, integrity} = (await npmRegistryUtils.fetchTarballURLAndSignature(registry.package, version))); ({signatures, integrity} = (await npmRegistryUtils.fetchTarballURLAndSignature(registry.package, version)));
npmRegistryUtils.verifySignature({signatures, integrity, packageName: registry.package, version}); await npmRegistryUtils.verifySignature({signatures, integrity, packageName: registry.package, version});
// @ts-expect-error ignore readonly // @ts-expect-error ignore readonly
build[1] = Buffer.from(integrity.slice(`sha512-`.length), `base64`).toString(`hex`); build[1] = Buffer.from(integrity.slice(`sha512-`.length), `base64`).toString(`hex`);
} }

View File

@ -92,7 +92,7 @@ export async function fetchUrlStream(input: string | URL, init?: RequestInit) {
let ProxyAgent: typeof import('undici').ProxyAgent; let ProxyAgent: typeof import('undici').ProxyAgent;
async function getProxyAgent(input: string | URL) { export async function getProxyAgent(input: string | URL) {
const {getProxyForUrl} = await import(`proxy-from-env`); const {getProxyForUrl} = await import(`proxy-from-env`);
// @ts-expect-error - The internal implementation is compatible with a WHATWG URL instance // @ts-expect-error - The internal implementation is compatible with a WHATWG URL instance

View File

@ -1,9 +1,14 @@
import * as sigstoreTuf from '@sigstore/tuf';
import {UsageError} from 'clipanion'; import {UsageError} from 'clipanion';
import {createVerify} from 'crypto'; import assert from 'node:assert';
import * as crypto from 'node:crypto';
import * as path from 'node:path';
import defaultConfig from '../config.json'; import defaultConfig from '../config.json';
import {shouldSkipIntegrityCheck} from './corepackUtils'; import {shouldSkipIntegrityCheck} from './corepackUtils';
import * as debugUtils from './debugUtils';
import * as folderUtils from './folderUtils';
import * as httpUtils from './httpUtils'; import * as httpUtils from './httpUtils';
// load abbreviated metadata as that's all we need for these calls // load abbreviated metadata as that's all we need for these calls
@ -32,7 +37,83 @@ export async function fetchAsJson(packageName: string, version?: string) {
return httpUtils.fetchAsJson(`${npmRegistryUrl}/${packageName}${version ? `/${version}` : ``}`, {headers}); return httpUtils.fetchAsJson(`${npmRegistryUrl}/${packageName}${version ? `/${version}` : ``}`, {headers});
} }
export function verifySignature({signatures, integrity, packageName, version}: { interface KeyInfo {
keyid: string;
// base64 encoded DER SPKI
keyData: string;
}
async function fetchSigstoreTufKeys(): Promise<Array<KeyInfo> | null> {
// This follows the implementation for npm.
// See https://github.com/npm/cli/blob/3a80a7b7d168c23b5e297cba7b47ba5b9875934d/lib/utils/verify-signatures.js#L174
let keysRaw: string;
try {
// @ts-expect-error inject custom fetch into monkey-patched `tuf-js` module.
globalThis.tufJsFetch = async (input: string) => {
const agent = await httpUtils.getProxyAgent(input);
return await globalThis.fetch(input, {
dispatcher: agent,
});
};
const sigstoreTufClient = await sigstoreTuf.initTUF({
cachePath: path.join(folderUtils.getCorepackHomeFolder(), `_tuf`),
});
keysRaw = await sigstoreTufClient.getTarget(`registry.npmjs.org/keys.json`);
} catch (error) {
console.warn(`Warning: Failed to get signing keys from Sigstore TUF repo`, error);
return null;
}
// The format of the key file is undocumented but follows `PublicKey` from
// sigstore/protobuf-specs.
// See https://github.com/sigstore/protobuf-specs/blob/main/gen/pb-typescript/src/__generated__/sigstore_common.ts
const keysFromSigstore = JSON.parse(keysRaw) as {keys: Array<{keyId: string, publicKey: {rawBytes: string, keyDetails: string}}>};
return keysFromSigstore.keys.filter(key => {
if (key.publicKey.keyDetails === `PKIX_ECDSA_P256_SHA_256`) {
return true;
} else {
debugUtils.log(`Unsupported verification key type ${key.publicKey.keyDetails}`);
return false;
}
}).map(k => ({
keyid: k.keyId,
keyData: k.publicKey.rawBytes,
}));
}
async function getVerificationKeys(): Promise<Array<KeyInfo>> {
let keys: Array<{keyid: string, key: string}>;
if (process.env.COREPACK_INTEGRITY_KEYS) {
// We use the format of the `GET /-/npm/v1/keys` endpoint with `npm` instead
// of `keys` as the wrapping key.
const keysFromEnv = JSON.parse(process.env.COREPACK_INTEGRITY_KEYS) as {npm: Array<{keyid: string, key: string}>};
keys = keysFromEnv.npm;
debugUtils.log(`Using COREPACK_INTEGRITY_KEYS to verify signatures: ${keys.map(k => k.keyid).join(`, `)}`);
return keys.map(k => ({
keyid: k.keyid,
keyData: k.key,
}));
}
const sigstoreKeys = await fetchSigstoreTufKeys();
if (sigstoreKeys) {
debugUtils.log(`Using NPM keys from @sigstore/tuf to verify signatures: ${sigstoreKeys.map(k => k.keyid).join(`, `)}`);
return sigstoreKeys;
}
debugUtils.log(`Falling back to built-in npm verification keys`);
return defaultConfig.keys.npm.map(k => ({
keyid: k.keyid,
keyData: k.key,
}));
}
let verificationKeysCache: Promise<Array<KeyInfo>> | null = null;
export async function verifySignature({signatures, integrity, packageName, version}: {
signatures: Array<{keyid: string, sig: string}>; signatures: Array<{keyid: string, sig: string}>;
integrity: string; integrity: string;
packageName: string; packageName: string;
@ -40,30 +121,28 @@ export function verifySignature({signatures, integrity, packageName, version}: {
}) { }) {
if (!Array.isArray(signatures) || !signatures.length) throw new Error(`No compatible signature found in package metadata`); if (!Array.isArray(signatures) || !signatures.length) throw new Error(`No compatible signature found in package metadata`);
const {npm: trustedKeys} = process.env.COREPACK_INTEGRITY_KEYS ? if (!verificationKeysCache)
JSON.parse(process.env.COREPACK_INTEGRITY_KEYS) as typeof defaultConfig.keys : verificationKeysCache = getVerificationKeys();
defaultConfig.keys;
let signature: typeof signatures[0] | undefined; const keys = await verificationKeysCache;
let key!: string; const keyInfo = keys.find(({keyid}) => signatures.some(s => s.keyid === keyid));
for (const k of trustedKeys) { if (keyInfo == null)
signature = signatures.find(({keyid}) => keyid === k.keyid); throw new Error(`Cannot find key to verify signature. signature keys: ${signatures.map(s => s.keyid)}, verification keys: ${keys.map(k => k.keyid)}`);
if (signature != null) {
key = k.key; const signature = signatures.find(({keyid}) => keyid === keyInfo.keyid);
break; assert(signature);
}
} const verifier = crypto.createVerify(`SHA256`);
if (signature?.sig == null) throw new UsageError(`The package was not signed by any trusted keys: ${JSON.stringify({signatures, trustedKeys}, undefined, 2)}`); const payload = `${packageName}@${version}:${integrity}`;
verifier.end(payload);
const key = crypto.createPublicKey({key: Buffer.from(keyInfo.keyData, `base64`), format: `der`, type: `spki`});
const valid = verifier.verify(key, signature.sig, `base64`);
const verifier = createVerify(`SHA256`);
verifier.end(`${packageName}@${version}:${integrity}`);
const valid = verifier.verify(
`-----BEGIN PUBLIC KEY-----\n${key}\n-----END PUBLIC KEY-----`,
signature.sig,
`base64`,
);
if (!valid) { if (!valid) {
throw new Error(`Signature does not match`); throw new Error(
`Signature verification failed for ${payload} with key ${keyInfo.keyid}\n` +
`If you are using a custom registry you can set COREPACK_INTEGRITY_KEYS.`,
);
} }
} }
@ -74,7 +153,7 @@ export async function fetchLatestStableVersion(packageName: string) {
if (!shouldSkipIntegrityCheck()) { if (!shouldSkipIntegrityCheck()) {
try { try {
verifySignature({ await verifySignature({
packageName, version, packageName, version,
integrity, signatures, integrity, signatures,
}); });

View File

@ -7,6 +7,14 @@ import {gzipSync} from 'node:zlib';
let privateKey, keyid; let privateKey, keyid;
switch (process.env.TEST_INTEGRITY) { switch (process.env.TEST_INTEGRITY) {
case `invalid_npm_signature`: {
// Claim to use a known NPM signing key but provide an invalid signature
keyid = `SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U`;
({privateKey} = generateKeyPairSync(`ec`, {
namedCurve: `sect239k1`,
}));
break;
}
case `invalid_signature`: { case `invalid_signature`: {
({privateKey} = generateKeyPairSync(`ec`, { ({privateKey} = generateKeyPairSync(`ec`, {
namedCurve: `sect239k1`, namedCurve: `sect239k1`,
@ -195,6 +203,7 @@ if (process.env.AUTH_TYPE === `PROXY`) {
} }
server.listen(0, `localhost`); server.listen(0, `localhost`);
server.unref();
await once(server, `listening`); await once(server, `listening`);
const {address, port} = server.address(); const {address, port} = server.address();
@ -222,17 +231,8 @@ switch (process.env.AUTH_TYPE) {
default: throw new Error(`Invalid AUTH_TYPE in env`, {cause: process.env.AUTH_TYPE}); default: throw new Error(`Invalid AUTH_TYPE in env`, {cause: process.env.AUTH_TYPE});
} }
if (process.env.NOCK_ENV === `replay`) { globalThis.fetch.passthroughUrls.push(
const originalFetch = globalThis.fetch; process.env.AUTH_TYPE === `PROXY` ?
globalThis.fetch = function fetch(i) { `http://example.com` :
if (!`${i}`.startsWith( `http://${address.includes(`:`) ? `[${address}]` : address}:${port}`,
process.env.AUTH_TYPE === `PROXY` ? );
`http://example.com` :
`http://${address.includes(`:`) ? `[${address}]` : address}:${port}`))
throw new Error(`Unexpected request to ${i}`);
return Reflect.apply(originalFetch, this, arguments);
};
}
server.unref();

View File

@ -8,7 +8,13 @@ export async function runCli(cwd: PortablePath, argv: Array<string>, withCustomR
const err: Array<Buffer> = []; const err: Array<Buffer> = [];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [`--no-warnings`, ...(withCustomRegistry ? [`--import`, pathToFileURL(path.join(__dirname, `_registryServer.mjs`)) as any as string] : [`-r`, require.resolve(`./recordRequests.js`)]), require.resolve(`../dist/corepack.js`), ...argv], { const child = spawn(process.execPath, [
`--no-warnings`,
...(withCustomRegistry ? [`--import`, pathToFileURL(path.join(__dirname, `_registryServer.mjs`)).toString()] : []),
`--require`, require.resolve(`./recordRequests.js`),
require.resolve(`../dist/corepack.js`),
...argv,
], {
cwd: npath.fromPortablePath(cwd), cwd: npath.fromPortablePath(cwd),
env: process.env, env: process.env,
stdio: `pipe`, stdio: `pipe`,

View File

@ -1585,7 +1585,7 @@ describe(`handle integrity checks`, () => {
await xfs.mktempPromise(async cwd => { await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({ await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
stderr: expect.stringContaining(`Signature does not match`), stderr: expect.stringContaining(`Signature verification failed`),
stdout: ``, stdout: ``,
}); });
}); });
@ -1596,7 +1596,7 @@ describe(`handle integrity checks`, () => {
await xfs.mktempPromise(async cwd => { await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm@latest`, `--version`], true)).resolves.toMatchObject({ await expect(runCli(cwd, [`pnpm@latest`, `--version`], true)).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
stderr: expect.stringContaining(`Signature does not match`), stderr: expect.stringContaining(`Signature verification failed`),
stdout: ``, stdout: ``,
}); });
}); });
@ -1636,23 +1636,23 @@ describe(`handle integrity checks`, () => {
await xfs.mktempPromise(async cwd => { await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({ await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
stderr: expect.stringContaining(`Signature does not match`), stderr: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
stdout: ``, stdout: ``,
}); });
// A second time to validate the invalid version was not cached. // A second time to validate the invalid version was not cached.
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({ await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
stderr: expect.stringContaining(`Signature does not match`), stderr: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
stdout: ``, stdout: ``,
}); });
await expect(runCli(cwd, [`yarn`, `--version`], true)).resolves.toMatchObject({ await expect(runCli(cwd, [`yarn`, `--version`], true)).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
stderr: expect.stringContaining(`Signature does not match`), stderr: expect.stringContaining(`Signature verification failed for yarn@1.9998.9999`),
stdout: ``, stdout: ``,
}); });
await expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({ await expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
stdout: expect.stringContaining(`Signature does not match`), stdout: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
stderr: ``, stderr: ``,
}); });
}); });
@ -1663,12 +1663,12 @@ describe(`handle integrity checks`, () => {
await xfs.mktempPromise(async cwd => { await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`yarn@1.9998.9999`, `--version`], true)).resolves.toMatchObject({ await expect(runCli(cwd, [`yarn@1.9998.9999`, `--version`], true)).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
stderr: expect.stringContaining(`Signature does not match`), stderr: expect.stringContaining(`Signature verification failed for yarn@1.9998.9999`),
stdout: ``, stdout: ``,
}); });
await expect(runCli(cwd, [`use`, `yarn@1.9998.9999`], true)).resolves.toMatchObject({ await expect(runCli(cwd, [`use`, `yarn@1.9998.9999`], true)).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
stdout: expect.stringContaining(`Signature does not match`), stdout: expect.stringContaining(`Signature verification failed for yarn@1.9998.9999`),
stderr: ``, stderr: ``,
}); });
}); });
@ -1707,4 +1707,43 @@ describe(`handle integrity checks`, () => {
}); });
}); });
}); });
it(`rejects invalid signatures for npm keys`, async () => {
process.env.TEST_INTEGRITY = `invalid_npm_signature`; // See `_registryServer.mjs`
process.env.COREPACK_DEFAULT_TO_LATEST = `1`; // Necessary as the version defined in `config.json` does not exist on the custom registry.
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
stdout: ``,
});
await expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
stderr: ``,
});
});
});
it(`rejects invalid signatures when TUF repo cannot be reached`, async () => {
process.env.TEST_INTEGRITY = `invalid_npm_signature`; // See `_registryServer.mjs`
process.env.COREPACK_DEFAULT_TO_LATEST = `1`; // Necessary as the version defined in `config.json` does not exist on the custom registry.
process.env.BLOCK_SIGSTORE_TUF_REQUESTS = `1`;
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
stdout: ``,
});
await expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
stderr: expect.stringContaining(`Failed to get signing keys from Sigstore TUF repo`),
});
});
});
}); });

Binary file not shown.

View File

@ -2,6 +2,7 @@
const path = require(`node:path`); const path = require(`node:path`);
const crypto = require(`node:crypto`); const crypto = require(`node:crypto`);
const SQLite3 = require(`better-sqlite3`); const SQLite3 = require(`better-sqlite3`);
const FakeTimers = require(`@sinonjs/fake-timers`);
const db = new SQLite3(path.join(__dirname, `nocks.db`)); const db = new SQLite3(path.join(__dirname, `nocks.db`));
process.once(`exit`, () => { process.once(`exit`, () => {
@ -26,7 +27,7 @@ function getRequestHash(input, init) {
if (init) { if (init) {
for (const key in init) { for (const key in init) {
if (init[key] === undefined) continue; if (init[key] === undefined || key === `signal`) continue;
switch (key) { switch (key) {
case `headers`: case `headers`:
@ -41,11 +42,20 @@ function getRequestHash(input, init) {
return hash.digest(); return hash.digest();
} }
const originalFetch = globalThis.fetch;
const passthroughUrls = [];
if (process.env.NOCK_ENV === `record`) { if (process.env.NOCK_ENV === `record`) {
const insertNockStatement = db.prepare(`INSERT OR REPLACE INTO nocks (hash, body, headers, status) VALUES (?, ?, jsonb(?), ?)`); const insertNockStatement = db.prepare(`INSERT OR REPLACE INTO nocks (hash, body, headers, status) VALUES (?, ?, jsonb(?), ?)`);
const realFetch = globalThis.fetch;
globalThis.fetch = async (input, init) => { globalThis.fetch = async (input, init) => {
const response = await realFetch(input, init); if (passthroughUrls.some(passThrough => input.toString().startsWith(passThrough)))
return originalFetch(input, init);
if (process.env.BLOCK_SIGSTORE_TUF_REQUESTS && input.toString().startsWith(`https://tuf-repo-cdn.sigstore.dev`))
throw new Error(`Request to Sigstore TUF repository are blocked`);
const response = await originalFetch(input, init);
const data = await response.arrayBuffer(); const data = await response.arrayBuffer();
const minimalHeaders = new Headers(); const minimalHeaders = new Headers();
@ -69,14 +79,30 @@ if (process.env.NOCK_ENV === `record`) {
headers: minimalHeaders, headers: minimalHeaders,
}); });
}; };
} else if (process.env.NOCK_ENV === `replay`) { } else {
FakeTimers.install({
// When you re-record requests this needs to be set to the time of the
// recording so that TUF accepts recorded requests.
now: new Date(`2025-04-09`),
toFake: [`Date`],
});
const getNockStatement = db.prepare(`SELECT body, json(headers) as headers, status FROM nocks WHERE hash = ?`); const getNockStatement = db.prepare(`SELECT body, json(headers) as headers, status FROM nocks WHERE hash = ?`);
globalThis.fetch = async (input, init) => { globalThis.fetch = async (input, init) => {
if (passthroughUrls.some(passThrough => input.toString().startsWith(passThrough)))
return originalFetch(input, init);
if (process.env.BLOCK_SIGSTORE_TUF_REQUESTS && input.toString().startsWith(`https://tuf-repo-cdn.sigstore.dev`))
throw new Error(`Request to Sigstore TUF repository are blocked`);
const requestHash = getRequestHash(input, init); const requestHash = getRequestHash(input, init);
const mock = getNockStatement.get(requestHash); const mock = getNockStatement.get(requestHash);
if (!mock) throw new Error(`No mock found for ${input}; run the tests with NOCK_ENV=record to generate one`); if (!mock) {
// Crash process so that corepack cannot catch this error
console.error(Error(`No mock found for ${input}; run the tests with NOCK_ENV=record to generate one`));
process.exit(10);
}
return new Response(mock.body, { return new Response(mock.body, {
status: mock.status, status: mock.status,
@ -84,3 +110,5 @@ if (process.env.NOCK_ENV === `record`) {
}); });
}; };
} }
globalThis.fetch.passthroughUrls = passthroughUrls;

View File

@ -523,6 +523,41 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@sigstore/protobuf-specs@npm:^0.4.0":
version: 0.4.0
resolution: "@sigstore/protobuf-specs@npm:0.4.0"
checksum: 10c0/5b9e074ad132b977050cbd9431c09ea88b21af266dae91dda8d51e29c7b295e73e3be255c10d68874259326229dde1805dd1f5ff29082d2f3d32a932809816eb
languageName: node
linkType: hard
"@sigstore/tuf@npm:^3.1.0":
version: 3.1.0
resolution: "@sigstore/tuf@npm:3.1.0"
dependencies:
"@sigstore/protobuf-specs": "npm:^0.4.0"
tuf-js: "npm:^3.0.1"
checksum: 10c0/940237295bec3817ef4dbfd48de8b9a73b4e297966c05e81b6103747904def999f27499adb3de572407f2c72c6f28d2c699a6c8446be808b599c427a9903f081
languageName: node
linkType: hard
"@sinonjs/commons@npm:^3.0.1":
version: 3.0.1
resolution: "@sinonjs/commons@npm:3.0.1"
dependencies:
type-detect: "npm:4.0.8"
checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403
languageName: node
linkType: hard
"@sinonjs/fake-timers@npm:^14.0.0":
version: 14.0.0
resolution: "@sinonjs/fake-timers@npm:14.0.0"
dependencies:
"@sinonjs/commons": "npm:^3.0.1"
checksum: 10c0/0abdbd8e202570ab1774f307b58f686838151c5842340b2022a81462437f3b923ae57f6662d1002d882189dd98300d5d4c5f13c11f40b2661bc559f26c4320e8
languageName: node
linkType: hard
"@stylistic/eslint-plugin-jsx@npm:^3.1.0": "@stylistic/eslint-plugin-jsx@npm:^3.1.0":
version: 3.1.0 version: 3.1.0
resolution: "@stylistic/eslint-plugin-jsx@npm:3.1.0" resolution: "@stylistic/eslint-plugin-jsx@npm:3.1.0"
@ -552,6 +587,23 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@tufjs/canonical-json@npm:2.0.0":
version: 2.0.0
resolution: "@tufjs/canonical-json@npm:2.0.0"
checksum: 10c0/52c5ffaef1483ed5c3feedfeba26ca9142fa386eea54464e70ff515bd01c5e04eab05d01eff8c2593291dcaf2397ca7d9c512720e11f52072b04c47a5c279415
languageName: node
linkType: hard
"@tufjs/models@npm:3.0.1":
version: 3.0.1
resolution: "@tufjs/models@npm:3.0.1"
dependencies:
"@tufjs/canonical-json": "npm:2.0.0"
minimatch: "npm:^9.0.5"
checksum: 10c0/0b2022589139102edf28f7fdcd094407fc98ac25bf530ebcf538dd63152baea9b6144b713c8dfc4f6b7580adeff706ab6ecc5f9716c4b816e58a04419abb1926
languageName: node
linkType: hard
"@types/debug@npm:^4.1.5": "@types/debug@npm:^4.1.5":
version: 4.1.12 version: 4.1.12
resolution: "@types/debug@npm:4.1.12" resolution: "@types/debug@npm:4.1.12"
@ -1301,6 +1353,8 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "corepack@workspace:." resolution: "corepack@workspace:."
dependencies: dependencies:
"@sigstore/tuf": "npm:^3.1.0"
"@sinonjs/fake-timers": "npm:^14.0.0"
"@types/debug": "npm:^4.1.5" "@types/debug": "npm:^4.1.5"
"@types/node": "npm:^20.4.6" "@types/node": "npm:^20.4.6"
"@types/proxy-from-env": "npm:^1" "@types/proxy-from-env": "npm:^1"
@ -1371,7 +1425,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0": "debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.0":
version: 4.4.0 version: 4.4.0
resolution: "debug@npm:4.4.0" resolution: "debug@npm:4.4.0"
dependencies: dependencies:
@ -2819,7 +2873,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"make-fetch-happen@npm:^14.0.3": "make-fetch-happen@npm:^14.0.1, make-fetch-happen@npm:^14.0.3":
version: 14.0.3 version: 14.0.3
resolution: "make-fetch-happen@npm:14.0.3" resolution: "make-fetch-happen@npm:14.0.3"
dependencies: dependencies:
@ -2878,7 +2932,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"minimatch@npm:^9.0.4": "minimatch@npm:^9.0.4, minimatch@npm:^9.0.5":
version: 9.0.5 version: 9.0.5
resolution: "minimatch@npm:9.0.5" resolution: "minimatch@npm:9.0.5"
dependencies: dependencies:
@ -4123,6 +4177,28 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tuf-js@npm:3.0.1":
version: 3.0.1
resolution: "tuf-js@npm:3.0.1"
dependencies:
"@tufjs/models": "npm:3.0.1"
debug: "npm:^4.3.6"
make-fetch-happen: "npm:^14.0.1"
checksum: 10c0/4214dd6bb1ec8a6cadbc5690e5a8556de0306f0e95022e54fc7c0ff9dbcc229ab379fd4b048511387f9c0023ea8f8c35acd8f7313f6cbc94a1b8af8b289f62ad
languageName: node
linkType: hard
"tuf-js@patch:tuf-js@npm%3A3.0.1#~/.yarn/patches/tuf-js-npm-3.0.1-9135d15fbd.patch":
version: 3.0.1
resolution: "tuf-js@patch:tuf-js@npm%3A3.0.1#~/.yarn/patches/tuf-js-npm-3.0.1-9135d15fbd.patch::version=3.0.1&hash=208643"
dependencies:
"@tufjs/models": "npm:3.0.1"
debug: "npm:^4.3.6"
make-fetch-happen: "npm:^14.0.1"
checksum: 10c0/95371d3b42a808c85b139971f72f1f28e1f19ad04f6d82052b311c5cd47ec7e27718a2f5fe9a329f29bf433c958483f64e941d6193c75394c0cdf544eb4a43b1
languageName: node
linkType: hard
"tunnel-agent@npm:^0.6.0": "tunnel-agent@npm:^0.6.0":
version: 0.6.0 version: 0.6.0
resolution: "tunnel-agent@npm:0.6.0" resolution: "tunnel-agent@npm:0.6.0"
@ -4148,6 +4224,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"type-detect@npm:4.0.8":
version: 4.0.8
resolution: "type-detect@npm:4.0.8"
checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd
languageName: node
linkType: hard
"typed-array-buffer@npm:^1.0.3": "typed-array-buffer@npm:^1.0.3":
version: 1.0.3 version: 1.0.3
resolution: "typed-array-buffer@npm:1.0.3" resolution: "typed-array-buffer@npm:1.0.3"