@empire-builder-kit/nx 1.0.0-rc.2 → 1.0.0-rc.7
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/package.json +4 -4
- package/src/developer-agent/operation-registry.js +13 -1
- package/src/developer-agent/operation-registry.js.map +1 -1
- package/src/executors/blueprint-trust/blueprint-trust.js +6 -0
- package/src/executors/blueprint-trust/blueprint-trust.js.map +1 -1
- package/src/generators/post-generation-install.d.ts +1 -0
- package/src/generators/post-generation-install.js +134 -67
- package/src/generators/post-generation-install.js.map +1 -1
- package/src/generators/preset/tool-versions.d.ts +5 -5
- package/src/generators/preset/tool-versions.js +4 -4
- package/src/generators/preset/workspace-files.d.ts +3 -3
- package/src/generators/preset/workspace-files.js +63 -5
- package/src/generators/preset/workspace-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +1 -1
- package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-data-retention-job-files.js +4 -3
- package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js +4 -3
- package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +1 -1
- package/src/generators/slice/built-in-plan-testing-files.js +92 -5
- package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
- package/src/generators/workload/frozen-container.d.ts +3 -2
- package/src/generators/workload/frozen-container.js +6 -4
- package/src/generators/workload/frozen-container.js.map +1 -1
- package/src/git-policy/routing.d.ts +2 -0
- package/src/git-policy/routing.js +12 -1
- package/src/git-policy/routing.js.map +1 -1
- package/src/local-dev/environment-inspector.d.ts +1 -0
- package/src/local-dev/environment-inspector.js +21 -9
- package/src/local-dev/environment-inspector.js.map +1 -1
- package/src/supply-chain/blueprint-verification-execution.js +17 -2
- package/src/supply-chain/blueprint-verification-execution.js.map +1 -1
- package/src/supply-chain/generated-command.js +130 -43
- package/src/supply-chain/generated-command.js.map +1 -1
- package/src/supply-chain/generated-scanner-command.js +113 -54
- package/src/supply-chain/generated-scanner-command.js.map +1 -1
- package/src/supply-chain/inventory.d.ts +17 -0
- package/src/supply-chain/inventory.js +114 -3
- package/src/supply-chain/inventory.js.map +1 -1
- package/src/supply-chain/pnpm-toolchain-authority.d.ts +4 -3
- package/src/supply-chain/pnpm-toolchain-authority.js +20 -34
- package/src/supply-chain/pnpm-toolchain-authority.js.map +1 -1
- package/src/supply-chain/policy.js +4 -1
- package/src/supply-chain/policy.js.map +1 -1
- package/src/supply-chain/scanner.js +10 -1
- package/src/supply-chain/scanner.js.map +1 -1
|
@@ -6,6 +6,8 @@ function renderSupplyChainCheckCommand() {
|
|
|
6
6
|
createSupplyChainInventory,
|
|
7
7
|
createInstalledLicenseFindings,
|
|
8
8
|
createSupplyChainScannerInput,
|
|
9
|
+
assertWorkspaceImporterCoverage,
|
|
10
|
+
deriveWorkspaceManifestPathsFromPnpmLock,
|
|
9
11
|
discoverLifecycleScripts,
|
|
10
12
|
evaluateSupplyChainPolicy,
|
|
11
13
|
frameworkActionPinInventory,
|
|
@@ -15,6 +17,7 @@ function renderSupplyChainCheckCommand() {
|
|
|
15
17
|
assertSupplyChainScannerSubject,
|
|
16
18
|
requireActiveVendoredSourceRegistry,
|
|
17
19
|
validateReviewedBuildPermissions,
|
|
20
|
+
workspacePackageGlobs,
|
|
18
21
|
vendoredSourceRegistryDigest,
|
|
19
22
|
verifyActionPinInventory,
|
|
20
23
|
} from '@empire-builder-kit/nx/supply-chain';
|
|
@@ -164,48 +167,51 @@ function hasExactKeys(value, keys) {
|
|
|
164
167
|
);
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
throw new Error('pnpm workspace inventory did not return JSON.');
|
|
190
|
-
}
|
|
191
|
-
if (projects.length === 0 || projects.length > 2_000) {
|
|
192
|
-
throw new Error('pnpm workspace inventory is empty or exceeds 2,000 projects.');
|
|
193
|
-
}
|
|
194
|
-
const manifests = {};
|
|
195
|
-
for (const [index, project] of projects.entries()) {
|
|
196
|
-
if (!project || typeof project !== 'object' || typeof project.path !== 'string') {
|
|
197
|
-
throw new Error('pnpm workspace project ' + index + ' has no exact path.');
|
|
170
|
+
function discoverGlobbedManifestPaths(globs) {
|
|
171
|
+
// Walk only the directory depth each glob declares so a manifest present on
|
|
172
|
+
// disk but absent from the lockfile importers is still observed.
|
|
173
|
+
const discovered = new Set();
|
|
174
|
+
const walk = (segments, index, prefix) => {
|
|
175
|
+
if (index === segments.length) {
|
|
176
|
+
const manifest = prefix ? prefix + '/package.json' : 'package.json';
|
|
177
|
+
if (existsSync(insideWorkspace(manifest))) discovered.add(manifest);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const segment = segments[index];
|
|
181
|
+
const directory = prefix ? insideWorkspace(prefix) : workspaceRoot;
|
|
182
|
+
if (!existsSync(directory) || !statSync(directory).isDirectory()) return;
|
|
183
|
+
if (segment === '*' || segment === '**') {
|
|
184
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
185
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
186
|
+
if (entry.name === 'node_modules') continue;
|
|
187
|
+
const next = prefix ? prefix + '/' + entry.name : entry.name;
|
|
188
|
+
walk(segments, index + 1, next);
|
|
189
|
+
if (segment === '**') walk(segments, index, next);
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
198
192
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
193
|
+
if (segment.includes('*')) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
'Workspace package glob segment ' + segment + ' is not supported by the manifest drift sweep.',
|
|
196
|
+
);
|
|
203
197
|
}
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
walk(segments, index + 1, prefix ? prefix + '/' + segment : segment);
|
|
199
|
+
};
|
|
200
|
+
for (const glob of globs) {
|
|
201
|
+
walk(glob.split('/').filter((segment) => segment !== '' && segment !== '.'), 0, '');
|
|
206
202
|
}
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
return [...discovered];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function workspaceManifests(lockfile, workspacePolicy) {
|
|
207
|
+
const importerPaths = deriveWorkspaceManifestPathsFromPnpmLock(lockfile);
|
|
208
|
+
assertWorkspaceImporterCoverage(
|
|
209
|
+
discoverGlobbedManifestPaths(workspacePackageGlobs(workspacePolicy)),
|
|
210
|
+
importerPaths,
|
|
211
|
+
);
|
|
212
|
+
const manifests = {};
|
|
213
|
+
for (const path of importerPaths) {
|
|
214
|
+
manifests[path] = readJson(path);
|
|
209
215
|
}
|
|
210
216
|
return manifests;
|
|
211
217
|
}
|
|
@@ -392,6 +398,11 @@ const workspacePolicy = readBounded(
|
|
|
392
398
|
MAX_INPUT_BYTES,
|
|
393
399
|
'pnpm workspace policy',
|
|
394
400
|
);
|
|
401
|
+
const lockfile = readBounded(
|
|
402
|
+
'pnpm-lock.yaml',
|
|
403
|
+
MAX_LOCK_BYTES,
|
|
404
|
+
'pnpm lockfile',
|
|
405
|
+
);
|
|
395
406
|
const packageMetadata = installedPackageMetadata();
|
|
396
407
|
const lifecycleScripts = discoverLifecycleScripts(packageMetadata);
|
|
397
408
|
const buildPermissions = records(
|
|
@@ -434,8 +445,8 @@ const inventory = createSupplyChainInventory({
|
|
|
434
445
|
'supply-chain/downloaded-tools.json',
|
|
435
446
|
),
|
|
436
447
|
lifecycleScripts,
|
|
437
|
-
lockfile
|
|
438
|
-
manifests: workspaceManifests(),
|
|
448
|
+
lockfile,
|
|
449
|
+
manifests: workspaceManifests(lockfile, workspacePolicy),
|
|
439
450
|
nodeVersion: readBounded(
|
|
440
451
|
'.node-version',
|
|
441
452
|
64,
|
|
@@ -464,7 +475,7 @@ const containerInventoryDigest = digest({
|
|
|
464
475
|
if (options.protected) {
|
|
465
476
|
const normalizedScanner = createSupplyChainScannerInput(scannerInput);
|
|
466
477
|
const requiredCoverage = [
|
|
467
|
-
|
|
478
|
+
'npm',
|
|
468
479
|
...(inventory.containers.length > 0 || workflowContainers.length > 0
|
|
469
480
|
? ['container']
|
|
470
481
|
: []),
|
|
@@ -486,6 +497,78 @@ if (options.protected) {
|
|
|
486
497
|
resultDigests.push(reference);
|
|
487
498
|
const result = JSON.parse(bytes);
|
|
488
499
|
if (reference.coverage === 'npm') {
|
|
500
|
+
const packageManager =
|
|
501
|
+
/^pnpm@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\+sha512\.[a-f0-9]{128}$/.exec(
|
|
502
|
+
inventory.packageManager,
|
|
503
|
+
);
|
|
504
|
+
if (!packageManager?.[1]) {
|
|
505
|
+
throw new Error(
|
|
506
|
+
'Supply-chain inventory package manager is not one exact pnpm authority.',
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
const expectedPackageManagerInput = {
|
|
510
|
+
results: [
|
|
511
|
+
{
|
|
512
|
+
packages: [
|
|
513
|
+
{
|
|
514
|
+
package: {
|
|
515
|
+
ecosystem: 'npm',
|
|
516
|
+
name: 'pnpm',
|
|
517
|
+
version: packageManager[1],
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
};
|
|
524
|
+
const expectedPackageManagerInputBytes =
|
|
525
|
+
JSON.stringify(expectedPackageManagerInput, null, 2) + '\n';
|
|
526
|
+
if (
|
|
527
|
+
!hasExactKeys(result, [
|
|
528
|
+
'kind',
|
|
529
|
+
'lockfile',
|
|
530
|
+
'packageManager',
|
|
531
|
+
'schemaVersion',
|
|
532
|
+
]) ||
|
|
533
|
+
result.kind !== 'ebk-osv-npm-results' ||
|
|
534
|
+
result.schemaVersion !== 1 ||
|
|
535
|
+
!hasExactKeys(result.packageManager, [
|
|
536
|
+
'authority',
|
|
537
|
+
'input',
|
|
538
|
+
'inputSha256',
|
|
539
|
+
'result',
|
|
540
|
+
]) ||
|
|
541
|
+
result.packageManager.authority !== inventory.packageManager ||
|
|
542
|
+
JSON.stringify(canonical(result.packageManager.input)) !==
|
|
543
|
+
JSON.stringify(canonical(expectedPackageManagerInput)) ||
|
|
544
|
+
result.packageManager.inputSha256 !==
|
|
545
|
+
digest(expectedPackageManagerInputBytes)
|
|
546
|
+
) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
'npm scanner result does not bind the exact package-manager authority and custom input.',
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
const packageManagerResults = result.packageManager.result;
|
|
552
|
+
const scannedPackageManagers =
|
|
553
|
+
packageManagerResults && Array.isArray(packageManagerResults.results)
|
|
554
|
+
? packageManagerResults.results.flatMap((entry) =>
|
|
555
|
+
entry && Array.isArray(entry.packages)
|
|
556
|
+
? entry.packages
|
|
557
|
+
.map((scanned) => scanned?.package)
|
|
558
|
+
.filter(Boolean)
|
|
559
|
+
: [],
|
|
560
|
+
)
|
|
561
|
+
: [];
|
|
562
|
+
if (
|
|
563
|
+
scannedPackageManagers.length !== 1 ||
|
|
564
|
+
scannedPackageManagers[0].ecosystem !== 'npm' ||
|
|
565
|
+
scannedPackageManagers[0].name !== 'pnpm' ||
|
|
566
|
+
scannedPackageManagers[0].version !== packageManager[1]
|
|
567
|
+
) {
|
|
568
|
+
throw new Error(
|
|
569
|
+
'npm scanner result does not prove the exact package-manager scan.',
|
|
570
|
+
);
|
|
571
|
+
}
|
|
489
572
|
normalizedVulnerabilities.push(
|
|
490
573
|
...normalizeOsvScannerResults({
|
|
491
574
|
coverage: 'npm',
|
|
@@ -494,7 +577,11 @@ if (options.protected) {
|
|
|
494
577
|
resultGroups: [
|
|
495
578
|
{
|
|
496
579
|
context: { releases: [], scope: 'unknown', slices: [] },
|
|
497
|
-
document: result,
|
|
580
|
+
document: result.lockfile,
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
context: { releases: [], scope: 'unknown', slices: [] },
|
|
584
|
+
document: packageManagerResults,
|
|
498
585
|
},
|
|
499
586
|
],
|
|
500
587
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-command.js","sourceRoot":"","sources":["../../../../../packages/nx/src/supply-chain/generated-command.ts"],"names":[],"mappings":";;AAAA,
|
|
1
|
+
{"version":3,"file":"generated-command.js","sourceRoot":"","sources":["../../../../../packages/nx/src/supply-chain/generated-command.ts"],"names":[],"mappings":";;AAAA,sEAiwBC;AAjwBD,SAAgB,6BAA6B;IAC3C,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+vBlB,CAAC;AACF,CAAC"}
|
|
@@ -9,12 +9,15 @@ function renderSupplyChainScannerCommand() {
|
|
|
9
9
|
createInstalledLicenseFindings,
|
|
10
10
|
createSupplyChainInventory,
|
|
11
11
|
createSupplyChainScannerInput,
|
|
12
|
+
assertWorkspaceImporterCoverage,
|
|
13
|
+
deriveWorkspaceManifestPathsFromPnpmLock,
|
|
12
14
|
discoverLifecycleScripts,
|
|
13
15
|
frameworkActionPinInventory,
|
|
14
16
|
normalizeOsvScannerResults,
|
|
15
17
|
requireActiveVendoredSourceRegistry,
|
|
16
18
|
vendoredSourceRegistryDigest,
|
|
17
19
|
verifyActionPinInventory,
|
|
20
|
+
workspacePackageGlobs,
|
|
18
21
|
} from '@empire-builder-kit/nx/supply-chain';
|
|
19
22
|
import { spawnSync } from 'node:child_process';
|
|
20
23
|
import { createHash } from 'node:crypto';
|
|
@@ -170,52 +173,51 @@ function atomicWrite(path, contents, mode = 0o600) {
|
|
|
170
173
|
renameSync(temporary, target);
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
function
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
encoding: 'utf8',
|
|
183
|
-
maxBuffer: MAX_INPUT_BYTES,
|
|
184
|
-
shell: false,
|
|
185
|
-
},
|
|
186
|
-
)
|
|
187
|
-
: spawnSync('pnpm', arguments_, {
|
|
188
|
-
cwd: workspaceRoot,
|
|
189
|
-
encoding: 'utf8',
|
|
190
|
-
maxBuffer: MAX_INPUT_BYTES,
|
|
191
|
-
shell: false,
|
|
192
|
-
});
|
|
193
|
-
if (listed.status !== 0) {
|
|
194
|
-
throw new Error(
|
|
195
|
-
'pnpm workspace inventory failed: ' +
|
|
196
|
-
String(listed.stderr || listed.stdout).trim().slice(0, 2_000),
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
const parsed = JSON.parse(listed.stdout);
|
|
200
|
-
const projects = Array.isArray(parsed) ? parsed : [parsed];
|
|
201
|
-
if (projects.length === 0 || projects.length > 2_000) {
|
|
202
|
-
throw new Error('pnpm workspace inventory is empty or exceeds 2,000 projects.');
|
|
203
|
-
}
|
|
204
|
-
const manifests = {};
|
|
205
|
-
for (const project of projects) {
|
|
206
|
-
if (!project || typeof project.path !== 'string') {
|
|
207
|
-
throw new Error('pnpm workspace project has no exact path.');
|
|
176
|
+
function discoverGlobbedManifestPaths(globs) {
|
|
177
|
+
// Walk only the directory depth each glob declares so a manifest present on
|
|
178
|
+
// disk but absent from the lockfile importers is still observed.
|
|
179
|
+
const discovered = new Set();
|
|
180
|
+
const walk = (segments, index, prefix) => {
|
|
181
|
+
if (index === segments.length) {
|
|
182
|
+
const manifest = prefix ? prefix + '/package.json' : 'package.json';
|
|
183
|
+
if (existsSync(insideWorkspace(manifest))) discovered.add(manifest);
|
|
184
|
+
return;
|
|
208
185
|
}
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
212
|
-
|
|
186
|
+
const segment = segments[index];
|
|
187
|
+
const directory = prefix ? insideWorkspace(prefix) : workspaceRoot;
|
|
188
|
+
if (!existsSync(directory) || !statSync(directory).isDirectory()) return;
|
|
189
|
+
if (segment === '*' || segment === '**') {
|
|
190
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
191
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
192
|
+
if (entry.name === 'node_modules') continue;
|
|
193
|
+
const next = prefix ? prefix + '/' + entry.name : entry.name;
|
|
194
|
+
walk(segments, index + 1, next);
|
|
195
|
+
if (segment === '**') walk(segments, index, next);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
213
198
|
}
|
|
214
|
-
|
|
215
|
-
|
|
199
|
+
if (segment.includes('*')) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
'Workspace package glob segment ' + segment + ' is not supported by the manifest drift sweep.',
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
walk(segments, index + 1, prefix ? prefix + '/' + segment : segment);
|
|
205
|
+
};
|
|
206
|
+
for (const glob of globs) {
|
|
207
|
+
walk(glob.split('/').filter((segment) => segment !== '' && segment !== '.'), 0, '');
|
|
216
208
|
}
|
|
217
|
-
|
|
218
|
-
|
|
209
|
+
return [...discovered];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function workspaceManifests(lockfile, workspacePolicy) {
|
|
213
|
+
const importerPaths = deriveWorkspaceManifestPathsFromPnpmLock(lockfile);
|
|
214
|
+
assertWorkspaceImporterCoverage(
|
|
215
|
+
discoverGlobbedManifestPaths(workspacePackageGlobs(workspacePolicy)),
|
|
216
|
+
importerPaths,
|
|
217
|
+
);
|
|
218
|
+
const manifests = {};
|
|
219
|
+
for (const path of importerPaths) {
|
|
220
|
+
manifests[path] = readJson(path);
|
|
219
221
|
}
|
|
220
222
|
return manifests;
|
|
221
223
|
}
|
|
@@ -528,6 +530,16 @@ const containers = sourceFilesBelow(
|
|
|
528
530
|
'.',
|
|
529
531
|
(path) => /(?:^|\/)Dockerfile$/i.test(path) || /\.Dockerfile$/i.test(path),
|
|
530
532
|
);
|
|
533
|
+
const lockfile = readBounded(
|
|
534
|
+
'pnpm-lock.yaml',
|
|
535
|
+
MAX_LOCK_BYTES,
|
|
536
|
+
'pnpm lockfile',
|
|
537
|
+
);
|
|
538
|
+
const workspacePolicy = readBounded(
|
|
539
|
+
'pnpm-workspace.yaml',
|
|
540
|
+
MAX_INPUT_BYTES,
|
|
541
|
+
'pnpm workspace policy',
|
|
542
|
+
);
|
|
531
543
|
const inventory = createSupplyChainInventory({
|
|
532
544
|
containers,
|
|
533
545
|
downloadedTools: records(
|
|
@@ -535,8 +547,8 @@ const inventory = createSupplyChainInventory({
|
|
|
535
547
|
'supply-chain/downloaded-tools.json',
|
|
536
548
|
),
|
|
537
549
|
lifecycleScripts: discoverLifecycleScripts(metadata),
|
|
538
|
-
lockfile
|
|
539
|
-
manifests: workspaceManifests(),
|
|
550
|
+
lockfile,
|
|
551
|
+
manifests: workspaceManifests(lockfile, workspacePolicy),
|
|
540
552
|
nodeVersion: readBounded(
|
|
541
553
|
'.node-version',
|
|
542
554
|
64,
|
|
@@ -552,18 +564,14 @@ const inventory = createSupplyChainInventory({
|
|
|
552
564
|
revision: record.identity.revision,
|
|
553
565
|
sha256: record.retrieval.sha256,
|
|
554
566
|
})),
|
|
555
|
-
workspacePolicy
|
|
556
|
-
'pnpm-workspace.yaml',
|
|
557
|
-
MAX_INPUT_BYTES,
|
|
558
|
-
'pnpm workspace policy',
|
|
559
|
-
),
|
|
567
|
+
workspacePolicy,
|
|
560
568
|
});
|
|
561
569
|
const containerInventoryDigest = digest({
|
|
562
570
|
recipes: inventory.containers,
|
|
563
571
|
workflowExecutables: workflowContainers,
|
|
564
572
|
});
|
|
565
573
|
const requiredCoverage = [
|
|
566
|
-
|
|
574
|
+
'npm',
|
|
567
575
|
...(inventory.containers.length > 0 || workflowContainers.length > 0
|
|
568
576
|
? ['container']
|
|
569
577
|
: []),
|
|
@@ -577,14 +585,61 @@ const resultDigests = [];
|
|
|
577
585
|
const vulnerabilities = [];
|
|
578
586
|
|
|
579
587
|
if (requiredCoverage.includes('npm')) {
|
|
580
|
-
const
|
|
588
|
+
const lockfileResult = runOsv(osv.path, [
|
|
581
589
|
'scan',
|
|
582
590
|
'source',
|
|
583
591
|
'--format=json',
|
|
584
592
|
'--lockfile=pnpm-lock.yaml',
|
|
585
593
|
]);
|
|
594
|
+
const packageManager =
|
|
595
|
+
/^pnpm@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\+sha512\.[a-f0-9]{128}$/.exec(
|
|
596
|
+
inventory.packageManager,
|
|
597
|
+
);
|
|
598
|
+
if (!packageManager?.[1]) {
|
|
599
|
+
throw new Error(
|
|
600
|
+
'Supply-chain inventory package manager is not one exact pnpm authority.',
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
const packageManagerInputPath =
|
|
604
|
+
'.ebk/evidence/supply-chain/osv-package-manager-input.json';
|
|
605
|
+
const packageManagerInput = {
|
|
606
|
+
results: [
|
|
607
|
+
{
|
|
608
|
+
packages: [
|
|
609
|
+
{
|
|
610
|
+
package: {
|
|
611
|
+
ecosystem: 'npm',
|
|
612
|
+
name: 'pnpm',
|
|
613
|
+
version: packageManager[1],
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
],
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
};
|
|
620
|
+
const packageManagerInputBytes =
|
|
621
|
+
JSON.stringify(packageManagerInput, null, 2) + '\n';
|
|
622
|
+
atomicWrite(packageManagerInputPath, packageManagerInputBytes);
|
|
623
|
+
const packageManagerResult = runOsv(osv.path, [
|
|
624
|
+
'scan',
|
|
625
|
+
'source',
|
|
626
|
+
'--format=json',
|
|
627
|
+
'--all-packages',
|
|
628
|
+
'--lockfile=osv-scanner:' + packageManagerInputPath,
|
|
629
|
+
]);
|
|
586
630
|
const path = '.ebk/evidence/supply-chain/osv-npm.json';
|
|
587
|
-
const
|
|
631
|
+
const report = {
|
|
632
|
+
kind: 'ebk-osv-npm-results',
|
|
633
|
+
lockfile: lockfileResult,
|
|
634
|
+
packageManager: {
|
|
635
|
+
authority: inventory.packageManager,
|
|
636
|
+
input: packageManagerInput,
|
|
637
|
+
inputSha256: digest(packageManagerInputBytes),
|
|
638
|
+
result: packageManagerResult,
|
|
639
|
+
},
|
|
640
|
+
schemaVersion: 1,
|
|
641
|
+
};
|
|
642
|
+
const bytes = JSON.stringify(report, null, 2) + '\n';
|
|
588
643
|
atomicWrite(path, bytes);
|
|
589
644
|
resultDigests.push({ coverage: 'npm', path, sha256: digest(bytes) });
|
|
590
645
|
vulnerabilities.push(
|
|
@@ -595,7 +650,11 @@ if (requiredCoverage.includes('npm')) {
|
|
|
595
650
|
resultGroups: [
|
|
596
651
|
{
|
|
597
652
|
context: { releases: [], scope: 'unknown', slices: [] },
|
|
598
|
-
document:
|
|
653
|
+
document: lockfileResult,
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
context: { releases: [], scope: 'unknown', slices: [] },
|
|
657
|
+
document: packageManagerResult,
|
|
599
658
|
},
|
|
600
659
|
],
|
|
601
660
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-scanner-command.js","sourceRoot":"","sources":["../../../../../packages/nx/src/supply-chain/generated-scanner-command.ts"],"names":[],"mappings":";;AAAA,
|
|
1
|
+
{"version":3,"file":"generated-scanner-command.js","sourceRoot":"","sources":["../../../../../packages/nx/src/supply-chain/generated-scanner-command.ts"],"names":[],"mappings":";;AAAA,0EAowBC;AApwBD,SAAgB,+BAA+B;IAC7C,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkwBlB,CAAC;AACF,CAAC"}
|
|
@@ -72,6 +72,23 @@ export interface SupplyChainInventoryInput {
|
|
|
72
72
|
workspacePolicy: string;
|
|
73
73
|
}
|
|
74
74
|
export declare function discoverLifecycleScripts(packageMetadata: readonly unknown[]): readonly LifecycleScriptInventoryEntry[];
|
|
75
|
+
/**
|
|
76
|
+
* Returns the declared workspace package globs so a caller can enumerate the
|
|
77
|
+
* manifests that must appear as lockfile importers.
|
|
78
|
+
*/
|
|
79
|
+
export declare function workspacePackageGlobs(workspacePolicy: string): readonly string[];
|
|
80
|
+
/**
|
|
81
|
+
* Fails closed when a manifest the workspace globs select is absent from the
|
|
82
|
+
* lockfile importers. Deriving the manifest inventory from the importers
|
|
83
|
+
* alone cannot observe that drift: a manifest stripped from the lockfile
|
|
84
|
+
* simply disappears from the inventory instead of being reported.
|
|
85
|
+
*/
|
|
86
|
+
export declare function assertWorkspaceImporterCoverage(discoveredManifestPaths: readonly string[], importerManifestPaths: readonly string[]): void;
|
|
87
|
+
/**
|
|
88
|
+
* Derives the exact manifest inventory from a complete pnpm lockfile rather
|
|
89
|
+
* than executing a package manager selected from ambient PATH.
|
|
90
|
+
*/
|
|
91
|
+
export declare function deriveWorkspaceManifestPathsFromPnpmLock(lockfileSource: string): readonly string[];
|
|
75
92
|
export declare function validateReviewedBuildPermissions(workspacePolicy: string, permissions: readonly ReviewedBuildPermission[]): void;
|
|
76
93
|
export declare function validateLifecycleScriptInventory(lifecycleScripts: readonly LifecycleScriptInventoryEntry[], workspacePolicy: string): void;
|
|
77
94
|
export declare function scanContainerImageAuthorities(path: string, source: string): readonly ContainerImageAuthority[];
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SUPPLY_CHAIN_SUPPORTED_PLATFORMS = exports.SUPPLY_CHAIN_INVENTORY_SCHEMA_VERSION = void 0;
|
|
4
4
|
exports.discoverLifecycleScripts = discoverLifecycleScripts;
|
|
5
|
+
exports.workspacePackageGlobs = workspacePackageGlobs;
|
|
6
|
+
exports.assertWorkspaceImporterCoverage = assertWorkspaceImporterCoverage;
|
|
7
|
+
exports.deriveWorkspaceManifestPathsFromPnpmLock = deriveWorkspaceManifestPathsFromPnpmLock;
|
|
5
8
|
exports.validateReviewedBuildPermissions = validateReviewedBuildPermissions;
|
|
6
9
|
exports.validateLifecycleScriptInventory = validateLifecycleScriptInventory;
|
|
7
10
|
exports.scanContainerImageAuthorities = scanContainerImageAuthorities;
|
|
@@ -119,6 +122,109 @@ function parseLockfile(lockfile) {
|
|
|
119
122
|
}
|
|
120
123
|
return value;
|
|
121
124
|
}
|
|
125
|
+
const MAX_WORKSPACE_IMPORTERS = 2_000;
|
|
126
|
+
const MAX_WORKSPACE_IMPORTER_LENGTH = 1_024;
|
|
127
|
+
const MAX_WORKSPACE_IMPORTER_SEGMENT_LENGTH = 255;
|
|
128
|
+
const WINDOWS_RESERVED_WORKSPACE_SEGMENT = /^(?:aux|con|nul|prn|com[1-9]|lpt[1-9])(?:\..*)?$/iu;
|
|
129
|
+
function hasControlCharacter(value) {
|
|
130
|
+
return [...value].some((character) => {
|
|
131
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
132
|
+
return codePoint <= 0x1f || codePoint === 0x7f;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function workspaceManifestPath(importer) {
|
|
136
|
+
if (importer === '.')
|
|
137
|
+
return 'package.json';
|
|
138
|
+
const segments = importer.split('/');
|
|
139
|
+
if (importer.length === 0 ||
|
|
140
|
+
Buffer.byteLength(importer, 'utf8') > MAX_WORKSPACE_IMPORTER_LENGTH ||
|
|
141
|
+
importer.startsWith('/') ||
|
|
142
|
+
importer.endsWith('/') ||
|
|
143
|
+
importer.includes('\\') ||
|
|
144
|
+
/^[A-Za-z]:/u.test(importer) ||
|
|
145
|
+
hasControlCharacter(importer) ||
|
|
146
|
+
segments.some((segment) => segment === '' ||
|
|
147
|
+
segment === '.' ||
|
|
148
|
+
segment === '..' ||
|
|
149
|
+
Buffer.byteLength(segment, 'utf8') >
|
|
150
|
+
MAX_WORKSPACE_IMPORTER_SEGMENT_LENGTH ||
|
|
151
|
+
/[<>:"|?*]/u.test(segment) ||
|
|
152
|
+
/[. ]$/u.test(segment) ||
|
|
153
|
+
WINDOWS_RESERVED_WORKSPACE_SEGMENT.test(segment))) {
|
|
154
|
+
throw new errors_js_1.SupplyChainError('supply-chain-input-invalid', `pnpm-lock.yaml#importers.${importer}`, 'workspace importer must be one bounded repository-relative POSIX path');
|
|
155
|
+
}
|
|
156
|
+
return `${importer}/package.json`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Returns the declared workspace package globs so a caller can enumerate the
|
|
160
|
+
* manifests that must appear as lockfile importers.
|
|
161
|
+
*/
|
|
162
|
+
function workspacePackageGlobs(workspacePolicy) {
|
|
163
|
+
let parsed;
|
|
164
|
+
try {
|
|
165
|
+
parsed = (0, yaml_1.parse)(workspacePolicy);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw new errors_js_1.SupplyChainError('supply-chain-input-invalid', 'pnpm-workspace.yaml', `cannot be parsed: ${error instanceof Error ? error.message : String(error)}`);
|
|
169
|
+
}
|
|
170
|
+
const packages = (0, validation_js_1.isRecord)(parsed) ? parsed.packages : undefined;
|
|
171
|
+
if (!Array.isArray(packages) ||
|
|
172
|
+
packages.length === 0 ||
|
|
173
|
+
packages.length > MAX_WORKSPACE_IMPORTERS ||
|
|
174
|
+
packages.some((entry) => typeof entry !== 'string' ||
|
|
175
|
+
entry.trim() === '' ||
|
|
176
|
+
entry !== entry.trim() ||
|
|
177
|
+
entry.startsWith('/') ||
|
|
178
|
+
entry.split('/').includes('..'))) {
|
|
179
|
+
throw new errors_js_1.SupplyChainError('supply-chain-input-invalid', 'pnpm-workspace.yaml#packages', 'must declare bounded relative workspace package globs');
|
|
180
|
+
}
|
|
181
|
+
return packages;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Fails closed when a manifest the workspace globs select is absent from the
|
|
185
|
+
* lockfile importers. Deriving the manifest inventory from the importers
|
|
186
|
+
* alone cannot observe that drift: a manifest stripped from the lockfile
|
|
187
|
+
* simply disappears from the inventory instead of being reported.
|
|
188
|
+
*/
|
|
189
|
+
function assertWorkspaceImporterCoverage(discoveredManifestPaths, importerManifestPaths) {
|
|
190
|
+
const importers = new Set(importerManifestPaths);
|
|
191
|
+
const missing = [...new Set(discoveredManifestPaths)]
|
|
192
|
+
.filter((path) => !importers.has(path))
|
|
193
|
+
.sort(deterministic_order_js_1.compareUtf16);
|
|
194
|
+
if (missing.length > 0) {
|
|
195
|
+
throw new errors_js_1.SupplyChainError('supply-chain-lock-drift', 'pnpm-lock.yaml#importers', `is missing the importer for ${missing.join(', ')}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Derives the exact manifest inventory from a complete pnpm lockfile rather
|
|
200
|
+
* than executing a package manager selected from ambient PATH.
|
|
201
|
+
*/
|
|
202
|
+
function deriveWorkspaceManifestPathsFromPnpmLock(lockfileSource) {
|
|
203
|
+
const lockfile = parseLockfile(lockfileSource);
|
|
204
|
+
const importers = lockfile.importers;
|
|
205
|
+
const entries = Object.entries(importers).sort(([left], [right]) => (0, deterministic_order_js_1.compareUtf16)(left, right));
|
|
206
|
+
if (entries.length === 0 || entries.length > MAX_WORKSPACE_IMPORTERS) {
|
|
207
|
+
throw new errors_js_1.SupplyChainError('supply-chain-input-invalid', 'pnpm-lock.yaml#importers', `must contain from 1 through ${MAX_WORKSPACE_IMPORTERS} workspace importers`);
|
|
208
|
+
}
|
|
209
|
+
if (!Object.prototype.hasOwnProperty.call(importers, '.')) {
|
|
210
|
+
throw new errors_js_1.SupplyChainError('supply-chain-inventory-incomplete', 'pnpm-lock.yaml#importers', 'must contain the root workspace importer');
|
|
211
|
+
}
|
|
212
|
+
const paths = entries.map(([importer, value]) => {
|
|
213
|
+
if (!(0, validation_js_1.isRecord)(value)) {
|
|
214
|
+
throw new errors_js_1.SupplyChainError('supply-chain-input-invalid', `pnpm-lock.yaml#importers.${importer}`, 'workspace importer must be an object');
|
|
215
|
+
}
|
|
216
|
+
return workspaceManifestPath(importer);
|
|
217
|
+
});
|
|
218
|
+
const portableIdentities = new Set();
|
|
219
|
+
for (const path of paths) {
|
|
220
|
+
const identity = path.normalize('NFC').toLowerCase();
|
|
221
|
+
if (portableIdentities.has(identity)) {
|
|
222
|
+
throw new errors_js_1.SupplyChainError('supply-chain-input-invalid', 'pnpm-lock.yaml#importers', 'workspace importers must not alias on supported filesystems');
|
|
223
|
+
}
|
|
224
|
+
portableIdentities.add(identity);
|
|
225
|
+
}
|
|
226
|
+
return paths;
|
|
227
|
+
}
|
|
122
228
|
function collectDependencies(manifests, lockfile) {
|
|
123
229
|
const importers = lockfile.importers;
|
|
124
230
|
const entries = [];
|
|
@@ -637,8 +743,11 @@ function scanContainerRecipe(path, source) {
|
|
|
637
743
|
throw new errors_js_1.SupplyChainError('supply-chain-container-mutable', `${path}#corepack`, 'Corepack must install one exact pnpm artifact with a SHA-512 descriptor');
|
|
638
744
|
}
|
|
639
745
|
}
|
|
640
|
-
|
|
641
|
-
|
|
746
|
+
const fetchCommands = [
|
|
747
|
+
...source.matchAll(/^\s*RUN\s+.*\bpnpm\b.*\bfetch\b.*$/gim),
|
|
748
|
+
].map(([command]) => command ?? '');
|
|
749
|
+
for (const command of fetchCommands) {
|
|
750
|
+
if (!command.includes('--frozen-lockfile')) {
|
|
642
751
|
throw new errors_js_1.SupplyChainError('supply-chain-container-mutable', path, 'pnpm fetch must retain the reviewed lockfile');
|
|
643
752
|
}
|
|
644
753
|
}
|
|
@@ -655,8 +764,10 @@ function scanContainerRecipe(path, source) {
|
|
|
655
764
|
!command.includes('--prod') ||
|
|
656
765
|
!command.includes('--offline') ||
|
|
657
766
|
!command.includes('--frozen-lockfile') ||
|
|
767
|
+
!command.includes('--trust-lockfile') ||
|
|
768
|
+
!fetchCommands.some((fetch) => fetch.includes('--frozen-lockfile')) ||
|
|
658
769
|
command.includes('--legacy')) {
|
|
659
|
-
throw new errors_js_1.SupplyChainError('supply-chain-container-mutable', path, 'pnpm deploy must
|
|
770
|
+
throw new errors_js_1.SupplyChainError('supply-chain-container-mutable', path, 'pnpm deploy must trust one previously verified frozen lockfile and assemble its offline production closure with BuildKit network access disabled');
|
|
660
771
|
}
|
|
661
772
|
}
|
|
662
773
|
return {
|