@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.
Files changed (46) hide show
  1. package/package.json +4 -4
  2. package/src/developer-agent/operation-registry.js +13 -1
  3. package/src/developer-agent/operation-registry.js.map +1 -1
  4. package/src/executors/blueprint-trust/blueprint-trust.js +6 -0
  5. package/src/executors/blueprint-trust/blueprint-trust.js.map +1 -1
  6. package/src/generators/post-generation-install.d.ts +1 -0
  7. package/src/generators/post-generation-install.js +134 -67
  8. package/src/generators/post-generation-install.js.map +1 -1
  9. package/src/generators/preset/tool-versions.d.ts +5 -5
  10. package/src/generators/preset/tool-versions.js +4 -4
  11. package/src/generators/preset/workspace-files.d.ts +3 -3
  12. package/src/generators/preset/workspace-files.js +63 -5
  13. package/src/generators/preset/workspace-files.js.map +1 -1
  14. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +1 -1
  15. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
  16. package/src/generators/slice/built-in-plan-data-retention-job-files.js +4 -3
  17. package/src/generators/slice/built-in-plan-data-retention-job-files.js.map +1 -1
  18. package/src/generators/slice/built-in-plan-database-lifecycle-files.js +4 -3
  19. package/src/generators/slice/built-in-plan-database-lifecycle-files.js.map +1 -1
  20. package/src/generators/slice/built-in-plan-testing-files.js +92 -5
  21. package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
  22. package/src/generators/workload/frozen-container.d.ts +3 -2
  23. package/src/generators/workload/frozen-container.js +6 -4
  24. package/src/generators/workload/frozen-container.js.map +1 -1
  25. package/src/git-policy/routing.d.ts +2 -0
  26. package/src/git-policy/routing.js +12 -1
  27. package/src/git-policy/routing.js.map +1 -1
  28. package/src/local-dev/environment-inspector.d.ts +1 -0
  29. package/src/local-dev/environment-inspector.js +21 -9
  30. package/src/local-dev/environment-inspector.js.map +1 -1
  31. package/src/supply-chain/blueprint-verification-execution.js +17 -2
  32. package/src/supply-chain/blueprint-verification-execution.js.map +1 -1
  33. package/src/supply-chain/generated-command.js +130 -43
  34. package/src/supply-chain/generated-command.js.map +1 -1
  35. package/src/supply-chain/generated-scanner-command.js +113 -54
  36. package/src/supply-chain/generated-scanner-command.js.map +1 -1
  37. package/src/supply-chain/inventory.d.ts +17 -0
  38. package/src/supply-chain/inventory.js +114 -3
  39. package/src/supply-chain/inventory.js.map +1 -1
  40. package/src/supply-chain/pnpm-toolchain-authority.d.ts +4 -3
  41. package/src/supply-chain/pnpm-toolchain-authority.js +20 -34
  42. package/src/supply-chain/pnpm-toolchain-authority.js.map +1 -1
  43. package/src/supply-chain/policy.js +4 -1
  44. package/src/supply-chain/policy.js.map +1 -1
  45. package/src/supply-chain/scanner.js +10 -1
  46. 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 workspaceManifests() {
168
- const listed = spawnSync(
169
- 'pnpm',
170
- ['list', '--recursive', '--depth=-1', '--json'],
171
- {
172
- cwd: workspaceRoot,
173
- encoding: 'utf8',
174
- maxBuffer: MAX_INPUT_BYTES,
175
- shell: false,
176
- },
177
- );
178
- if (listed.status !== 0) {
179
- throw new Error(
180
- 'pnpm workspace inventory failed: ' +
181
- String(listed.stderr || listed.stdout).trim().slice(0, 2_000),
182
- );
183
- }
184
- let projects;
185
- try {
186
- const parsed = JSON.parse(listed.stdout);
187
- projects = Array.isArray(parsed) ? parsed : [parsed];
188
- } catch {
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
- const root = realpathSync(insideWorkspace(project.path));
200
- const child = relative(workspaceRoot, root);
201
- if (child === '..' || child.startsWith('..' + sep)) {
202
- throw new Error('pnpm workspace project escapes the repository.');
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
- const path = (child ? child.split(sep).join('/') + '/' : '') + 'package.json';
205
- manifests[path] = readJson(path);
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
- if (!Object.hasOwn(manifests, 'package.json')) {
208
- manifests['package.json'] = readJson('package.json');
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: readBounded('pnpm-lock.yaml', MAX_LOCK_BYTES, 'pnpm 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
- ...(inventory.resolvedPackages.length > 0 ? ['npm'] : []),
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,sEA0qBC;AA1qBD,SAAgB,6BAA6B;IAC3C,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwqBlB,CAAC;AACF,CAAC"}
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 workspaceManifests() {
174
- const arguments_ = ['list', '--recursive', '--depth=-1', '--json'];
175
- const listed =
176
- process.platform === 'win32'
177
- ? spawnSync(
178
- process.env.ComSpec ?? 'cmd.exe',
179
- ['/d', '/s', '/c', ['pnpm.cmd', ...arguments_].join(' ')],
180
- {
181
- cwd: workspaceRoot,
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 root = realpathSync(insideWorkspace(project.path));
210
- const child = relative(workspaceRoot, root);
211
- if (child === '..' || child.startsWith('..' + sep)) {
212
- throw new Error('pnpm workspace project escapes the repository.');
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
- const path = (child ? child.split(sep).join('/') + '/' : '') + 'package.json';
215
- manifests[path] = readJson(path);
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
- if (!Object.hasOwn(manifests, 'package.json')) {
218
- manifests['package.json'] = readJson('package.json');
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: readBounded('pnpm-lock.yaml', MAX_LOCK_BYTES, 'pnpm 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: readBounded(
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
- ...(inventory.resolvedPackages.length > 0 ? ['npm'] : []),
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 npmResult = runOsv(osv.path, [
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 bytes = JSON.stringify(npmResult, null, 2) + '\n';
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: npmResult,
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,0EAysBC;AAzsBD,SAAgB,+BAA+B;IAC7C,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAusBlB,CAAC;AACF,CAAC"}
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
- for (const fetch of source.matchAll(/^\s*RUN\s+.*\bpnpm\b.*\bfetch\b.*$/gim)) {
641
- if (!(fetch[0] ?? '').includes('--frozen-lockfile')) {
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 assemble one frozen, offline production closure with BuildKit network access disabled');
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 {