Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,13 @@ same major line. Should you need to upgrade to a new major, use an explicit
environment variables are required and as plain text. If you want to send an
empty password, explicitly set `COREPACK_NPM_PASSWORD` to an empty string.

- `COREPACK_ON_UNVERIFIED_DOWNLOAD` can be set to:
- `warn` (case insensitive): attempting to download an unsigned version without
providing a hash will emit a warning to stderr.
- `error` (case insensitive): attempting to download an unsigned version without
providing a hash will emit a warning to stderr.
- `ignore` (or any other unsupported value): disables that security feature.

- `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` are supported through
[`NODE_USE_ENV_PROXY=1`](https://nodejs.org/api/cli.html#node_use_env_proxy1).

Expand Down
13 changes: 12 additions & 1 deletion sources/corepackUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,19 @@ export async function installVersion(installTarget: string, locator: Locator, {s
build[1] = Buffer.from(integrity.slice(`sha512-`.length), `base64`).toString(`hex`);
}
}
if (build[1] && actualHash !== build[1])
if (!build[1]) {
const {COREPACK_ON_UNVERIFIED_DOWNLOAD} = process.env;
debugUtils.log(`No hash provided, and signature was not verified; checking COREPACK_ON_UNVERIFIED_DOWNLOAD, set to: ${COREPACK_ON_UNVERIFIED_DOWNLOAD}`);
switch (COREPACK_ON_UNVERIFIED_DOWNLOAD?.toUpperCase()) {
case `ERROR`:
throw new Error(`Integrity of ${locator.name}@${version} could not be verified. Downloading unverified versions is disabled by the COREPACK_ON_UNVERIFIED_DOWNLOAD env variable. Please provide a hash.`);

case `WARN`:
console.warn(`Integrity of ${locator.name}@${version} could not be verified. Consider providing a hash. Set COREPACK_ON_UNVERIFIED_DOWNLOAD to 'ignore' to remove this warning.`);
}
} else if (actualHash !== build[1]) {
throw new Error(`Mismatch hashes. Expected ${build[1]}, got ${actualHash}`);
}

const serializedHash = `${algo}.${actualHash}`;

Expand Down
4 changes: 3 additions & 1 deletion tests/Up.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ describe(`UpCommand`, () => {
packageManager: `yarn@2.1.0`,
});

process.env.COREPACK_ON_UNVERIFIED_DOWNLOAD = `warn`;

await expect(runCli(cwd, [`up`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stderr: `Integrity of yarn@2.4.3 could not be verified. Consider providing a hash. Set COREPACK_ON_UNVERIFIED_DOWNLOAD to 'ignore' to remove this warning.\n`,
stdout: expect.stringMatching(/^Installing yarn@2\.4\.3 in the project\.\.\.\n\n/),
});

Expand Down
4 changes: 3 additions & 1 deletion tests/Use.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,11 @@ describe(`UseCommand`, () => {
const subfolder = ppath.join(cwd, `subfolder`);
await xfs.mkdirPromise(subfolder);

process.env.COREPACK_ON_UNVERIFIED_DOWNLOAD = `warn`;

await expect(runCli(subfolder, [`use`, `yarn@2.2.2`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stderr: `Integrity of yarn@2.2.2 could not be verified. Consider providing a hash. Set COREPACK_ON_UNVERIFIED_DOWNLOAD to 'ignore' to remove this warning.\n`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
Expand Down
134 changes: 131 additions & 3 deletions tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,7 @@ it(`should support disabling the network accesses from the environment`, async (

await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `yarn@2.2.2`,
packageManager: `yarn@2.2.2+sha1.9aede2626b101719cbc1314d61def0742852ba11`,
});

await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
Expand All @@ -880,12 +880,12 @@ describe(`read-only and offline environment`, () => {

// Prepare fake project
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `yarn@2.2.2`,
packageManager: `yarn@2.2.2+sha1.9aede2626b101719cbc1314d61def0742852ba11`,
});

// $ corepack install
await expect(runCli(cwd, [`install`])).resolves.toMatchObject({
stdout: `Adding yarn@2.2.2 to the cache...\n`,
stdout: `Adding yarn@2.2.2+sha1.9aede2626b101719cbc1314d61def0742852ba11 to the cache...\n`,
stderr: ``,
exitCode: 0,
});
Expand Down Expand Up @@ -1459,6 +1459,134 @@ describe(`should pick up COREPACK_INTEGRITY_KEYS from env`, () => {
});
});

describe(`unverified downloads`, () => {
beforeEach(() => {
process.env.AUTH_TYPE = `COREPACK_NPM_TOKEN`; // See `_registryServer.mjs`
process.env.COREPACK_DEFAULT_TO_LATEST = `1`;
process.env.COREPACK_INTEGRITY_KEYS = `0`;
});

it(`from env variable`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {});

process.env.COREPACK_ON_UNVERIFIED_DOWNLOAD = `error`;
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`Downloading unverified versions is disabled by the COREPACK_ON_UNVERIFIED_DOWNLOAD env variable`),
});
await expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`Downloading unverified versions is disabled by the COREPACK_ON_UNVERIFIED_DOWNLOAD env variable`),
});

process.env.COREPACK_ON_UNVERIFIED_DOWNLOAD = `ignore`;
await expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``, // No warning expected
});

process.env.COREPACK_ON_UNVERIFIED_DOWNLOAD = `warn`;
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: expect.stringContaining(`Integrity of pnpm@1.9998.9999 could not be verified.`),
});
await expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``, // Already cached, no warning expected
});
});
});

it(`from .corepack.env file`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {});

await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_ON_UNVERIFIED_DOWNLOAD=error\n`);
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`Downloading unverified versions is disabled by the COREPACK_ON_UNVERIFIED_DOWNLOAD env variable`),
});
await expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`Downloading unverified versions is disabled by the COREPACK_ON_UNVERIFIED_DOWNLOAD env variable`),
});

await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_ON_UNVERIFIED_DOWNLOAD=ignore\n`);
await expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``, // No warning expected
});

await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_ON_UNVERIFIED_DOWNLOAD=warn\n`);
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: expect.stringContaining(`Integrity of pnpm@1.9998.9999 could not be verified.`),
});
await expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``, // Already cached, no warning expected
});
});
});

it(`from env file defined by COREPACK_ENV_FILE`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
});

await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_ON_UNVERIFIED_DOWNLOAD=error\n`);
await xfs.writeFilePromise(ppath.join(cwd, `.other.env` as Filename), `COREPACK_ON_UNVERIFIED_DOWNLOAD=warn\n`);

// By default, Corepack should be using .corepack.env and fail.
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`Downloading unverified versions is disabled by the COREPACK_ON_UNVERIFIED_DOWNLOAD env variable`),
});

process.env.COREPACK_ENV_FILE = `.other.env`;
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: expect.stringContaining(`Integrity of pnpm@1.9998.9999 could not be verified.`),
});
});
});

it(`from env even if there's a .corepack.env file`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {});

await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_ON_UNVERIFIED_DOWNLOAD=error\n`);

// By default, Corepack should be using .corepack.env (or the built-in ones on Node.js 18.x) and fail.
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`Downloading unverified versions is disabled by the COREPACK_ON_UNVERIFIED_DOWNLOAD env variable`),
});

process.env.COREPACK_ON_UNVERIFIED_DOWNLOAD = `warn`;
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: expect.stringContaining(`Integrity of pnpm@1.9998.9999 could not be verified`),
});
});
});
});

for (const authType of [`COREPACK_NPM_REGISTRY`, `COREPACK_NPM_TOKEN`, `COREPACK_NPM_PASSWORD`, `PROXY`]) {
describe(`custom registry with auth ${authType}`, () => {
beforeEach(() => {
Expand Down
Loading