mirror of https://github.com/nodejs/corepack.git
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:
parent
679bcefda5
commit
aa1b84939a
|
|
@ -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;
|
||||
13
README.md
13
README.md
|
|
@ -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).
|
||||
|
||||
- `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
|
||||
custom keys.
|
||||
instruct Corepack to skip signature verification, or to a JSON string
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
"license": "MIT",
|
||||
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728",
|
||||
"devDependencies": {
|
||||
"@sigstore/tuf": "^3.1.0",
|
||||
"@sinonjs/fake-timers": "^14.0.0",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/node": "^20.4.6",
|
||||
"@types/proxy-from-env": "^1",
|
||||
|
|
@ -43,7 +45,8 @@
|
|||
"which": "^5.0.0"
|
||||
},
|
||||
"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": {
|
||||
"build": "run clean && run build:bundle && tsx ./mkshims.ts",
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ export async function installVersion(installTarget: string, locator: Locator, {s
|
|||
if (signatures! == null || integrity! == null)
|
||||
({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
|
||||
build[1] = Buffer.from(integrity.slice(`sha512-`.length), `base64`).toString(`hex`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export async function fetchUrlStream(input: string | URL, init?: RequestInit) {
|
|||
|
||||
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`);
|
||||
|
||||
// @ts-expect-error - The internal implementation is compatible with a WHATWG URL instance
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import * as sigstoreTuf from '@sigstore/tuf';
|
||||
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 {shouldSkipIntegrityCheck} from './corepackUtils';
|
||||
import * as debugUtils from './debugUtils';
|
||||
import * as folderUtils from './folderUtils';
|
||||
import * as httpUtils from './httpUtils';
|
||||
|
||||
// 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});
|
||||
}
|
||||
|
||||
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}>;
|
||||
integrity: 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`);
|
||||
|
||||
const {npm: trustedKeys} = process.env.COREPACK_INTEGRITY_KEYS ?
|
||||
JSON.parse(process.env.COREPACK_INTEGRITY_KEYS) as typeof defaultConfig.keys :
|
||||
defaultConfig.keys;
|
||||
if (!verificationKeysCache)
|
||||
verificationKeysCache = getVerificationKeys();
|
||||
|
||||
let signature: typeof signatures[0] | undefined;
|
||||
let key!: string;
|
||||
for (const k of trustedKeys) {
|
||||
signature = signatures.find(({keyid}) => keyid === k.keyid);
|
||||
if (signature != null) {
|
||||
key = k.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (signature?.sig == null) throw new UsageError(`The package was not signed by any trusted keys: ${JSON.stringify({signatures, trustedKeys}, undefined, 2)}`);
|
||||
const keys = await verificationKeysCache;
|
||||
const keyInfo = keys.find(({keyid}) => signatures.some(s => s.keyid === keyid));
|
||||
if (keyInfo == null)
|
||||
throw new Error(`Cannot find key to verify signature. signature keys: ${signatures.map(s => s.keyid)}, verification keys: ${keys.map(k => k.keyid)}`);
|
||||
|
||||
const signature = signatures.find(({keyid}) => keyid === keyInfo.keyid);
|
||||
assert(signature);
|
||||
|
||||
const verifier = crypto.createVerify(`SHA256`);
|
||||
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) {
|
||||
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()) {
|
||||
try {
|
||||
verifySignature({
|
||||
await verifySignature({
|
||||
packageName, version,
|
||||
integrity, signatures,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@ import {gzipSync} from 'node:zlib';
|
|||
let privateKey, keyid;
|
||||
|
||||
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`: {
|
||||
({privateKey} = generateKeyPairSync(`ec`, {
|
||||
namedCurve: `sect239k1`,
|
||||
|
|
@ -195,6 +203,7 @@ if (process.env.AUTH_TYPE === `PROXY`) {
|
|||
}
|
||||
|
||||
server.listen(0, `localhost`);
|
||||
server.unref();
|
||||
await once(server, `listening`);
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
if (process.env.NOCK_ENV === `replay`) {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = function fetch(i) {
|
||||
if (!`${i}`.startsWith(
|
||||
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();
|
||||
globalThis.fetch.passthroughUrls.push(
|
||||
process.env.AUTH_TYPE === `PROXY` ?
|
||||
`http://example.com` :
|
||||
`http://${address.includes(`:`) ? `[${address}]` : address}:${port}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,13 @@ export async function runCli(cwd: PortablePath, argv: Array<string>, withCustomR
|
|||
const err: Array<Buffer> = [];
|
||||
|
||||
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),
|
||||
env: process.env,
|
||||
stdio: `pipe`,
|
||||
|
|
|
|||
|
|
@ -1585,7 +1585,7 @@ describe(`handle integrity checks`, () => {
|
|||
await xfs.mktempPromise(async cwd => {
|
||||
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stderr: expect.stringContaining(`Signature does not match`),
|
||||
stderr: expect.stringContaining(`Signature verification failed`),
|
||||
stdout: ``,
|
||||
});
|
||||
});
|
||||
|
|
@ -1596,7 +1596,7 @@ describe(`handle integrity checks`, () => {
|
|||
await xfs.mktempPromise(async cwd => {
|
||||
await expect(runCli(cwd, [`pnpm@latest`, `--version`], true)).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stderr: expect.stringContaining(`Signature does not match`),
|
||||
stderr: expect.stringContaining(`Signature verification failed`),
|
||||
stdout: ``,
|
||||
});
|
||||
});
|
||||
|
|
@ -1636,23 +1636,23 @@ describe(`handle integrity checks`, () => {
|
|||
await xfs.mktempPromise(async cwd => {
|
||||
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stderr: expect.stringContaining(`Signature does not match`),
|
||||
stderr: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
|
||||
stdout: ``,
|
||||
});
|
||||
// A second time to validate the invalid version was not cached.
|
||||
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stderr: expect.stringContaining(`Signature does not match`),
|
||||
stderr: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
|
||||
stdout: ``,
|
||||
});
|
||||
await expect(runCli(cwd, [`yarn`, `--version`], true)).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stderr: expect.stringContaining(`Signature does not match`),
|
||||
stderr: expect.stringContaining(`Signature verification failed for yarn@1.9998.9999`),
|
||||
stdout: ``,
|
||||
});
|
||||
await expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stdout: expect.stringContaining(`Signature does not match`),
|
||||
stdout: expect.stringContaining(`Signature verification failed for pnpm@1.9998.9999`),
|
||||
stderr: ``,
|
||||
});
|
||||
});
|
||||
|
|
@ -1663,12 +1663,12 @@ describe(`handle integrity checks`, () => {
|
|||
await xfs.mktempPromise(async cwd => {
|
||||
await expect(runCli(cwd, [`yarn@1.9998.9999`, `--version`], true)).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stderr: expect.stringContaining(`Signature does not match`),
|
||||
stderr: expect.stringContaining(`Signature verification failed for yarn@1.9998.9999`),
|
||||
stdout: ``,
|
||||
});
|
||||
await expect(runCli(cwd, [`use`, `yarn@1.9998.9999`], true)).resolves.toMatchObject({
|
||||
exitCode: 1,
|
||||
stdout: expect.stringContaining(`Signature does not match`),
|
||||
stdout: expect.stringContaining(`Signature verification failed for yarn@1.9998.9999`),
|
||||
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`),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
BIN
tests/nocks.db
BIN
tests/nocks.db
Binary file not shown.
|
|
@ -2,6 +2,7 @@
|
|||
const path = require(`node:path`);
|
||||
const crypto = require(`node:crypto`);
|
||||
const SQLite3 = require(`better-sqlite3`);
|
||||
const FakeTimers = require(`@sinonjs/fake-timers`);
|
||||
|
||||
const db = new SQLite3(path.join(__dirname, `nocks.db`));
|
||||
process.once(`exit`, () => {
|
||||
|
|
@ -26,7 +27,7 @@ function getRequestHash(input, init) {
|
|||
|
||||
if (init) {
|
||||
for (const key in init) {
|
||||
if (init[key] === undefined) continue;
|
||||
if (init[key] === undefined || key === `signal`) continue;
|
||||
|
||||
switch (key) {
|
||||
case `headers`:
|
||||
|
|
@ -41,11 +42,20 @@ function getRequestHash(input, init) {
|
|||
return hash.digest();
|
||||
}
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
const passthroughUrls = [];
|
||||
|
||||
if (process.env.NOCK_ENV === `record`) {
|
||||
const insertNockStatement = db.prepare(`INSERT OR REPLACE INTO nocks (hash, body, headers, status) VALUES (?, ?, jsonb(?), ?)`);
|
||||
const realFetch = globalThis.fetch;
|
||||
|
||||
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 minimalHeaders = new Headers();
|
||||
|
|
@ -69,14 +79,30 @@ if (process.env.NOCK_ENV === `record`) {
|
|||
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 = ?`);
|
||||
|
||||
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 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, {
|
||||
status: mock.status,
|
||||
|
|
@ -84,3 +110,5 @@ if (process.env.NOCK_ENV === `record`) {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
globalThis.fetch.passthroughUrls = passthroughUrls;
|
||||
|
|
|
|||
89
yarn.lock
89
yarn.lock
|
|
@ -523,6 +523,41 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 3.1.0
|
||||
resolution: "@stylistic/eslint-plugin-jsx@npm:3.1.0"
|
||||
|
|
@ -552,6 +587,23 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 4.1.12
|
||||
resolution: "@types/debug@npm:4.1.12"
|
||||
|
|
@ -1301,6 +1353,8 @@ __metadata:
|
|||
version: 0.0.0-use.local
|
||||
resolution: "corepack@workspace:."
|
||||
dependencies:
|
||||
"@sigstore/tuf": "npm:^3.1.0"
|
||||
"@sinonjs/fake-timers": "npm:^14.0.0"
|
||||
"@types/debug": "npm:^4.1.5"
|
||||
"@types/node": "npm:^20.4.6"
|
||||
"@types/proxy-from-env": "npm:^1"
|
||||
|
|
@ -1371,7 +1425,7 @@ __metadata:
|
|||
languageName: node
|
||||
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
|
||||
resolution: "debug@npm:4.4.0"
|
||||
dependencies:
|
||||
|
|
@ -2819,7 +2873,7 @@ __metadata:
|
|||
languageName: node
|
||||
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
|
||||
resolution: "make-fetch-happen@npm:14.0.3"
|
||||
dependencies:
|
||||
|
|
@ -2878,7 +2932,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^9.0.4":
|
||||
"minimatch@npm:^9.0.4, minimatch@npm:^9.0.5":
|
||||
version: 9.0.5
|
||||
resolution: "minimatch@npm:9.0.5"
|
||||
dependencies:
|
||||
|
|
@ -4123,6 +4177,28 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 0.6.0
|
||||
resolution: "tunnel-agent@npm:0.6.0"
|
||||
|
|
@ -4148,6 +4224,13 @@ __metadata:
|
|||
languageName: node
|
||||
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":
|
||||
version: 1.0.3
|
||||
resolution: "typed-array-buffer@npm:1.0.3"
|
||||
|
|
|
|||
Loading…
Reference in New Issue