@pnpm/installing.commands 1100.7.2 → 1100.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/handleIgnoredBuilds.js +2 -2
- package/lib/install.js +1 -1
- package/lib/installDeps.js +17 -1
- package/lib/verifyPacquetIdentity.d.ts +34 -0
- package/lib/verifyPacquetIdentity.js +87 -0
- package/package.json +49 -44
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { allowBuildKeyFromIgnoredBuild } from '@pnpm/building.policy';
|
|
1
2
|
import { writeSettings } from '@pnpm/config.writer';
|
|
2
|
-
import { parse } from '@pnpm/deps.path';
|
|
3
3
|
import { IgnoredBuildsError, } from '@pnpm/installing.deps-installer';
|
|
4
4
|
import { lexCompare } from '@pnpm/util.lex-comparator';
|
|
5
5
|
export async function handleIgnoredBuilds(opts, ignoredBuilds) {
|
|
@@ -29,6 +29,6 @@ async function writeIgnoredBuildsToAllowBuilds(opts, ignoredBuilds) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
function packageNamesFromIgnoredBuilds(ignoredBuilds) {
|
|
32
|
-
return Array.from(new Set(Array.from(ignoredBuilds).map(
|
|
32
|
+
return Array.from(new Set(Array.from(ignoredBuilds).map(allowBuildKeyFromIgnoredBuild))).sort(lexCompare);
|
|
33
33
|
}
|
|
34
34
|
//# sourceMappingURL=handleIgnoredBuilds.js.map
|
package/lib/install.js
CHANGED
|
@@ -208,7 +208,7 @@ by any dependencies, so it is an emulation of a flat node_modules',
|
|
|
208
208
|
name: '--ignore-workspace',
|
|
209
209
|
},
|
|
210
210
|
{
|
|
211
|
-
description:
|
|
211
|
+
description: 'If false, skips store integrity checks. These checks detect accidental corruption, not tampering by untrusted users with write access to the store',
|
|
212
212
|
name: '--[no-]verify-store-integrity',
|
|
213
213
|
},
|
|
214
214
|
{
|
package/lib/installDeps.js
CHANGED
|
@@ -21,6 +21,7 @@ import { setupPolicyHandlers } from './policyHandlers.js';
|
|
|
21
21
|
import { createMatcher, makeIgnorePatterns, matchDependencies, recursive, } from './recursive.js';
|
|
22
22
|
import { makeRunPacquet } from './runPacquet.js';
|
|
23
23
|
import { createWorkspaceSpecs, updateToWorkspacePackagesFromManifest } from './updateWorkspaceDependencies.js';
|
|
24
|
+
import { verifyPacquetIdentity } from './verifyPacquetIdentity.js';
|
|
24
25
|
const OVERWRITE_UPDATE_OPTIONS = {
|
|
25
26
|
allowNew: true,
|
|
26
27
|
update: false,
|
|
@@ -67,11 +68,26 @@ export async function installDeps(opts, params) {
|
|
|
67
68
|
// optional `@pacquet/<plat>-<arch>` binary sub-packages, so the
|
|
68
69
|
// resolved \`node_modules/.pnpm-config/<name>\` layout pacquet's
|
|
69
70
|
// wrapper expects is identical either way.
|
|
70
|
-
|
|
71
|
+
//
|
|
72
|
+
// `configDependencies` come from the repository's `pnpm-workspace.yaml`, so
|
|
73
|
+
// the declaration cannot be trusted to authorize spawning a native binary on
|
|
74
|
+
// its own. `verifyPacquetIdentity` confirms, against the canonical npm
|
|
75
|
+
// registry, that the installed bytes carry a valid registry signature for
|
|
76
|
+
// that `name@version` before we delegate; otherwise we fall back to pnpm's
|
|
77
|
+
// own engine.
|
|
78
|
+
const declaredPacquetConfigDepName = opts.configDependencies?.['@pnpm/pacquet'] != null
|
|
71
79
|
? '@pnpm/pacquet'
|
|
72
80
|
: opts.configDependencies?.pacquet != null
|
|
73
81
|
? 'pacquet'
|
|
74
82
|
: undefined;
|
|
83
|
+
const pacquetConfigDepName = declaredPacquetConfigDepName != null &&
|
|
84
|
+
await verifyPacquetIdentity(declaredPacquetConfigDepName, {
|
|
85
|
+
...opts,
|
|
86
|
+
lockfileDir: opts.lockfileDir ?? opts.dir,
|
|
87
|
+
rootDir: opts.lockfileDir ?? opts.dir,
|
|
88
|
+
})
|
|
89
|
+
? declaredPacquetConfigDepName
|
|
90
|
+
: undefined;
|
|
75
91
|
const runPacquet = pacquetConfigDepName != null
|
|
76
92
|
? makeRunPacquet({
|
|
77
93
|
lockfileDir: opts.lockfileDir ?? opts.dir,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CreateFetchFromRegistryOptions, RetryTimeoutOptions } from '@pnpm/network.fetch';
|
|
2
|
+
import type { Registries, RegistryConfig } from '@pnpm/types';
|
|
3
|
+
export interface VerifyPacquetIdentityOptions extends CreateFetchFromRegistryOptions {
|
|
4
|
+
lockfileDir: string;
|
|
5
|
+
rootDir: string;
|
|
6
|
+
registries: Registries;
|
|
7
|
+
configByUri?: Record<string, RegistryConfig>;
|
|
8
|
+
retry?: RetryTimeoutOptions;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
networkConcurrency?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Decides whether pnpm may spawn the pacquet binary installed under
|
|
14
|
+
* `node_modules/.pnpm-config/<packageName>` as an install engine.
|
|
15
|
+
*
|
|
16
|
+
* A repository declares pacquet in its `pnpm-workspace.yaml`
|
|
17
|
+
* `configDependencies` and controls the lockfile integrity and the registry
|
|
18
|
+
* the bytes came from — so the declaration alone cannot authorize running a
|
|
19
|
+
* native binary. This verifies that the exact bytes installed on disk (the
|
|
20
|
+
* `pacquet` shim and the host's `@pacquet/<platform>-<arch>` binary, which is
|
|
21
|
+
* what actually executes) carry a valid npm registry signature for that
|
|
22
|
+
* `name@version`, checked against npm's embedded public keys. The signature is
|
|
23
|
+
* verified over the *installed* integrity, so substituted or tampered bytes
|
|
24
|
+
* fail — and because the keys are embedded rather than fetched, a repository
|
|
25
|
+
* pointing the registry at a server it controls cannot supply its own key pair.
|
|
26
|
+
*
|
|
27
|
+
* Fails closed: if a declared pacquet's signature does not verify, or it cannot
|
|
28
|
+
* be verified (e.g. the registry is unreachable), this throws rather than
|
|
29
|
+
* silently running pnpm's own engine — a verification failure should be loud.
|
|
30
|
+
* The one exception is when pacquet simply has no binary for this platform: that
|
|
31
|
+
* is unavailability, not tampering, so it returns `false` and the caller uses
|
|
32
|
+
* pnpm's own install engine.
|
|
33
|
+
*/
|
|
34
|
+
export declare function verifyPacquetIdentity(packageName: 'pacquet' | '@pnpm/pacquet', opts: VerifyPacquetIdentityOptions): Promise<boolean>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { pickRegistryForPackage } from '@pnpm/config.pick-registry-for-package';
|
|
2
|
+
import { getNpmSigningKeys, verifyInstalledPackageSignatures, } from '@pnpm/deps.security.signatures';
|
|
3
|
+
import { PnpmError } from '@pnpm/error';
|
|
4
|
+
import { readEnvLockfile } from '@pnpm/lockfile.fs';
|
|
5
|
+
import { logger } from '@pnpm/logger';
|
|
6
|
+
import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header';
|
|
7
|
+
/**
|
|
8
|
+
* Decides whether pnpm may spawn the pacquet binary installed under
|
|
9
|
+
* `node_modules/.pnpm-config/<packageName>` as an install engine.
|
|
10
|
+
*
|
|
11
|
+
* A repository declares pacquet in its `pnpm-workspace.yaml`
|
|
12
|
+
* `configDependencies` and controls the lockfile integrity and the registry
|
|
13
|
+
* the bytes came from — so the declaration alone cannot authorize running a
|
|
14
|
+
* native binary. This verifies that the exact bytes installed on disk (the
|
|
15
|
+
* `pacquet` shim and the host's `@pacquet/<platform>-<arch>` binary, which is
|
|
16
|
+
* what actually executes) carry a valid npm registry signature for that
|
|
17
|
+
* `name@version`, checked against npm's embedded public keys. The signature is
|
|
18
|
+
* verified over the *installed* integrity, so substituted or tampered bytes
|
|
19
|
+
* fail — and because the keys are embedded rather than fetched, a repository
|
|
20
|
+
* pointing the registry at a server it controls cannot supply its own key pair.
|
|
21
|
+
*
|
|
22
|
+
* Fails closed: if a declared pacquet's signature does not verify, or it cannot
|
|
23
|
+
* be verified (e.g. the registry is unreachable), this throws rather than
|
|
24
|
+
* silently running pnpm's own engine — a verification failure should be loud.
|
|
25
|
+
* The one exception is when pacquet simply has no binary for this platform: that
|
|
26
|
+
* is unavailability, not tampering, so it returns `false` and the caller uses
|
|
27
|
+
* pnpm's own install engine.
|
|
28
|
+
*/
|
|
29
|
+
export async function verifyPacquetIdentity(packageName, opts) {
|
|
30
|
+
const trustedKeys = getNpmSigningKeys();
|
|
31
|
+
const toVerify = await collectPacquetPackagesToVerify(packageName, opts.rootDir, opts.registries);
|
|
32
|
+
if (toVerify == null) {
|
|
33
|
+
// pacquet has no installed binary for this platform — use pnpm's own engine.
|
|
34
|
+
return skip(opts.lockfileDir);
|
|
35
|
+
}
|
|
36
|
+
const getAuthHeader = createGetAuthHeaderByURI(opts.configByUri ?? {});
|
|
37
|
+
let result;
|
|
38
|
+
try {
|
|
39
|
+
result = await verifyInstalledPackageSignatures(toVerify, trustedKeys, getAuthHeader, opts);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
throw new PnpmError('PACQUET_IDENTITY_UNVERIFIABLE', `Refusing to use pacquet as the install engine: its npm registry signature could not be verified (${String(err)}).`, { hint: 'The registry must be reachable to verify the pacquet release declared in configDependencies. Remove pacquet from configDependencies to use pnpm\'s own install engine.' });
|
|
43
|
+
}
|
|
44
|
+
if (!result.verified) {
|
|
45
|
+
const detail = result.failures.map(({ name, version, reason }) => `${name}@${version}: ${reason}`).join('; ');
|
|
46
|
+
throw new PnpmError('PACQUET_IDENTITY_MISMATCH', `Refusing to use pacquet as the install engine: the bytes installed for "${packageName}" do not match a published, signed release (${detail}).`, { hint: 'This can indicate a tampered lockfile or a malicious registry. Remove pacquet from configDependencies if this is unexpected.' });
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
async function collectPacquetPackagesToVerify(packageName, rootDir, registries) {
|
|
51
|
+
const envLockfile = await readEnvLockfile(rootDir);
|
|
52
|
+
if (envLockfile == null)
|
|
53
|
+
return undefined;
|
|
54
|
+
const shim = envLockfile.importers['.']?.configDependencies?.[packageName];
|
|
55
|
+
if (shim == null)
|
|
56
|
+
return undefined;
|
|
57
|
+
const shimKey = `${packageName}@${shim.version}`;
|
|
58
|
+
const shimIntegrity = registryIntegrity(envLockfile.packages[shimKey]?.resolution);
|
|
59
|
+
if (shimIntegrity == null)
|
|
60
|
+
return undefined;
|
|
61
|
+
// Only the host's platform binary is ever spawned, so that's the one whose
|
|
62
|
+
// identity matters. If it isn't in the lockfile, pacquet couldn't run here.
|
|
63
|
+
const platformPkgName = `@pacquet/${process.platform}-${process.arch}`;
|
|
64
|
+
const platformVersion = envLockfile.snapshots[shimKey]?.optionalDependencies?.[platformPkgName];
|
|
65
|
+
if (platformVersion == null)
|
|
66
|
+
return undefined;
|
|
67
|
+
const platformKey = `${platformPkgName}@${platformVersion}`;
|
|
68
|
+
const platformIntegrity = registryIntegrity(envLockfile.packages[platformKey]?.resolution);
|
|
69
|
+
if (platformIntegrity == null)
|
|
70
|
+
return undefined;
|
|
71
|
+
return [
|
|
72
|
+
{ name: packageName, version: shim.version, registry: pickRegistryForPackage(registries, packageName), integrity: shimIntegrity },
|
|
73
|
+
{ name: platformPkgName, version: platformVersion, registry: pickRegistryForPackage(registries, platformPkgName), integrity: platformIntegrity },
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
function registryIntegrity(resolution) {
|
|
77
|
+
const integrity = resolution?.integrity;
|
|
78
|
+
return typeof integrity === 'string' && integrity ? integrity : undefined;
|
|
79
|
+
}
|
|
80
|
+
function skip(prefix) {
|
|
81
|
+
logger.warn({
|
|
82
|
+
message: "Not using pacquet as the install engine: no pacquet binary is installed for this platform. Using pnpm's own install engine.",
|
|
83
|
+
prefix,
|
|
84
|
+
});
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=verifyPacquetIdentity.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/installing.commands",
|
|
3
|
-
"version": "1100.7.
|
|
3
|
+
"version": "1100.7.3",
|
|
4
4
|
"description": "Commands for installation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -47,46 +47,51 @@
|
|
|
47
47
|
"ramda": "npm:@pnpm/ramda@0.28.1",
|
|
48
48
|
"render-help": "^2.0.0",
|
|
49
49
|
"version-selector-type": "^3.0.0",
|
|
50
|
-
"@pnpm/building.
|
|
51
|
-
"@pnpm/catalogs.types": "1100.0.0",
|
|
52
|
-
"@pnpm/cli.common-cli-options-help": "1100.0.1",
|
|
50
|
+
"@pnpm/building.policy": "1100.0.9",
|
|
53
51
|
"@pnpm/cli.command": "1100.0.1",
|
|
54
|
-
"@pnpm/
|
|
52
|
+
"@pnpm/catalogs.types": "1100.0.0",
|
|
53
|
+
"@pnpm/cli.common-cli-options-help": "1100.0.2",
|
|
54
|
+
"@pnpm/cli.utils": "1101.0.10",
|
|
55
55
|
"@pnpm/config.matcher": "1100.0.1",
|
|
56
|
-
"@pnpm/config.
|
|
57
|
-
"@pnpm/
|
|
56
|
+
"@pnpm/config.pick-registry-for-package": "1100.0.8",
|
|
57
|
+
"@pnpm/config.writer": "1100.0.12",
|
|
58
58
|
"@pnpm/constants": "1100.0.0",
|
|
59
|
-
"@pnpm/config.
|
|
60
|
-
"@pnpm/
|
|
59
|
+
"@pnpm/config.reader": "1101.7.0",
|
|
60
|
+
"@pnpm/deps.inspection.outdated": "1100.1.6",
|
|
61
|
+
"@pnpm/deps.status": "1100.0.23",
|
|
62
|
+
"@pnpm/deps.path": "1100.0.7",
|
|
63
|
+
"@pnpm/deps.security.signatures": "1101.2.0",
|
|
64
|
+
"@pnpm/error": "1100.0.0",
|
|
61
65
|
"@pnpm/fs.graceful-fs": "1100.1.0",
|
|
62
66
|
"@pnpm/fs.read-modules-dir": "1100.0.1",
|
|
63
|
-
"@pnpm/global.commands": "1100.0.
|
|
64
|
-
"@pnpm/
|
|
65
|
-
"@pnpm/
|
|
66
|
-
"@pnpm/installing.
|
|
67
|
-
"@pnpm/
|
|
68
|
-
"@pnpm/deps
|
|
69
|
-
"@pnpm/
|
|
70
|
-
"@pnpm/
|
|
71
|
-
"@pnpm/
|
|
72
|
-
"@pnpm/
|
|
67
|
+
"@pnpm/global.commands": "1100.0.26",
|
|
68
|
+
"@pnpm/hooks.pnpmfile": "1100.0.13",
|
|
69
|
+
"@pnpm/installing.context": "1100.0.16",
|
|
70
|
+
"@pnpm/installing.dedupe.check": "1100.0.10",
|
|
71
|
+
"@pnpm/building.after-install": "1101.0.20",
|
|
72
|
+
"@pnpm/installing.deps-installer": "1101.8.0",
|
|
73
|
+
"@pnpm/lockfile.fs": "1100.1.4",
|
|
74
|
+
"@pnpm/installing.env-installer": "1101.1.7",
|
|
75
|
+
"@pnpm/lockfile.types": "1100.0.10",
|
|
76
|
+
"@pnpm/network.fetch": "1100.1.1",
|
|
77
|
+
"@pnpm/network.auth-header": "1101.1.1",
|
|
78
|
+
"@pnpm/resolving.npm-resolver": "1101.5.1",
|
|
73
79
|
"@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
|
|
74
|
-
"@pnpm/
|
|
75
|
-
"@pnpm/
|
|
76
|
-
"@pnpm/
|
|
77
|
-
"@pnpm/
|
|
78
|
-
"@pnpm/store.connection-manager": "1100.2.
|
|
79
|
-
"@pnpm/workspace.project-manifest-
|
|
80
|
-
"@pnpm/workspace.projects-filter": "1100.0.
|
|
81
|
-
"@pnpm/workspace.
|
|
82
|
-
"@pnpm/workspace.
|
|
83
|
-
"@pnpm/workspace.projects-sorter": "1100.0.
|
|
84
|
-
"@pnpm/workspace.projects-graph": "1100.0.
|
|
80
|
+
"@pnpm/pkg-manifest.utils": "1100.2.3",
|
|
81
|
+
"@pnpm/resolving.resolver-base": "1100.4.1",
|
|
82
|
+
"@pnpm/store.controller": "1101.0.12",
|
|
83
|
+
"@pnpm/types": "1101.3.1",
|
|
84
|
+
"@pnpm/store.connection-manager": "1100.2.7",
|
|
85
|
+
"@pnpm/workspace.project-manifest-reader": "1100.0.11",
|
|
86
|
+
"@pnpm/workspace.projects-filter": "1100.0.19",
|
|
87
|
+
"@pnpm/workspace.project-manifest-writer": "1100.0.7",
|
|
88
|
+
"@pnpm/workspace.projects-reader": "1101.0.10",
|
|
89
|
+
"@pnpm/workspace.projects-sorter": "1100.0.6",
|
|
90
|
+
"@pnpm/workspace.projects-graph": "1100.0.16",
|
|
91
|
+
"@pnpm/workspace.state": "1100.0.20",
|
|
92
|
+
"@pnpm/workspace.workspace-manifest-writer": "1100.0.12",
|
|
85
93
|
"@pnpm/workspace.root-finder": "1100.0.1",
|
|
86
|
-
"@pnpm/
|
|
87
|
-
"@pnpm/workspace.workspace-manifest-writer": "1100.0.11",
|
|
88
|
-
"@pnpm/pkg-manifest.utils": "1100.2.2",
|
|
89
|
-
"@pnpm/installing.dedupe.check": "1100.0.9"
|
|
94
|
+
"@pnpm/pkg-manifest.reader": "1100.0.7"
|
|
90
95
|
},
|
|
91
96
|
"peerDependencies": {
|
|
92
97
|
"@pnpm/logger": "^1001.0.1"
|
|
@@ -108,19 +113,19 @@
|
|
|
108
113
|
"write-json-file": "^7.0.0",
|
|
109
114
|
"write-package": "7.2.0",
|
|
110
115
|
"write-yaml-file": "^6.0.0",
|
|
111
|
-
"@pnpm/
|
|
112
|
-
"@pnpm/installing.modules-yaml": "1100.0.
|
|
116
|
+
"@pnpm/installing.commands": "1100.7.3",
|
|
117
|
+
"@pnpm/installing.modules-yaml": "1100.0.8",
|
|
113
118
|
"@pnpm/logger": "1100.0.0",
|
|
114
|
-
"@pnpm/
|
|
115
|
-
"@pnpm/
|
|
116
|
-
"@pnpm/test-fixtures": "1100.0.0",
|
|
119
|
+
"@pnpm/prepare": "1100.0.14",
|
|
120
|
+
"@pnpm/assert-project": "1100.0.14",
|
|
117
121
|
"@pnpm/store.index": "1100.1.0",
|
|
122
|
+
"@pnpm/test-fixtures": "1100.0.0",
|
|
123
|
+
"@pnpm/testing.command-defaults": "1100.0.4",
|
|
124
|
+
"@pnpm/testing.registry-mock": "1100.0.4",
|
|
125
|
+
"@pnpm/testing.mock-agent": "1101.0.1",
|
|
118
126
|
"@pnpm/test-ipc-server": "1100.0.0",
|
|
119
|
-
"@pnpm/
|
|
120
|
-
"@pnpm/
|
|
121
|
-
"@pnpm/testing.mock-agent": "1101.0.0",
|
|
122
|
-
"@pnpm/workspace.projects-filter": "1100.0.18",
|
|
123
|
-
"@pnpm/worker": "1100.1.9"
|
|
127
|
+
"@pnpm/workspace.projects-filter": "1100.0.19",
|
|
128
|
+
"@pnpm/worker": "1100.1.10"
|
|
124
129
|
},
|
|
125
130
|
"engines": {
|
|
126
131
|
"node": ">=22.13"
|