import { createHash, generateKeyPairSync, sign } from 'node:crypto'; import { mkdtemp, mkdir, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { canonicalValueBytes } from '../../src/security/canonical-digest.ts'; // @ts-expect-error The production release checker is an executable ESM module. import { verifySupplyChainEvidence } from '../../scripts/supply-chain-check.mjs'; const sha256 = (value: string | Buffer) => createHash('sha256').update(value).digest('hex'); async function evidenceFixture(overrides: Record = {}) { const root = await mkdtemp(join(tmpdir(), 'portfolio-supply-chain-')); const evidence = join(root, 'evidence'); await mkdir(evidence); const packageJson = JSON.stringify({ name: 'portfolio', version: '1.0.0', devDependencies: { '@cyclonedx/cyclonedx-npm': '6.0.1', 'license-checker-rseidelsohn': '4.4.2' } }); const packageLock = JSON.stringify({ name: 'portfolio', version: '1.0.0', lockfileVersion: 3, packages: { '': { name: 'portfolio', version: '1.0.0' }, 'node_modules/@cyclonedx/cyclonedx-npm': { version: '6.0.1', integrity: 'sha512-test' }, 'node_modules/license-checker-rseidelsohn': { version: '4.4.2', integrity: 'sha512-test' }, } }); await writeFile(join(root, 'package.json'), packageJson); await writeFile(join(root, 'package-lock.json'), packageLock); const signingKeys = generateKeyPairSync('ed25519'); const trustPolicyPath = join(root, 'execution-trust.json'); await writeFile(trustPolicyPath, JSON.stringify({ schema: 'portfolio-execution-evidence-trust/v1', keyId: 'supply-chain-test-key', issuerEpoch: 1, scope: 'portfolio-execution-security-evidence/v1', publicKeyPem: signingKeys.publicKey.export({ type: 'spki', format: 'pem' }), revoked: false, expectedImageRoles: ['coordinator', 'matcher', 'worker-a', 'worker-b'], })); const executionPayload = overrides.executionPayload ?? { schema: 'portfolio-execution-security-evidence/v1', keyId: 'supply-chain-test-key', issuerEpoch: 1, scope: 'portfolio-execution-security-evidence/v1', generatedAt: '2026-09-02T00:00:00Z', expiresAt: '2026-09-03T00:00:00Z', images: ['coordinator', 'matcher', 'worker-a', 'worker-b'].map((role, index) => ({ role, image: `registry.invalid/portfolio-${role}@sha256:${String(index + 1).repeat(64)}`, provenanceVerified: true, critical: 0, high: 0, })), nativeTools: [{ name: 'nginx', version: '1.29.1', sha256: 'a'.repeat(64), signatureVerified: true, critical: 0, high: 0 }], repository: { commitSha: 'b'.repeat(40), secretScanPassed: true, historyScanPassed: true, ciActionsPinned: true, distributionNoticesComplete: true, }, }; const executionEnvelope = overrides.executionEnvelope ?? { schema: 'portfolio-execution-security-evidence-envelope/v1', payload: executionPayload, signature: { algorithm: 'Ed25519', encoding: 'base64url-nopad', value: sign(null, canonicalValueBytes(executionPayload), signingKeys.privateKey).toString('base64url'), }, }; const reports = { 'npm-audit-production.json': JSON.stringify(overrides.productionAudit ?? { auditReportVersion: 2, vulnerabilities: {}, metadata: { vulnerabilities: { info: 0, low: 0, moderate: 0, high: 0, critical: 0, total: 0 } } }), 'npm-audit-development.json': JSON.stringify(overrides.developmentAudit ?? { auditReportVersion: 2, vulnerabilities: {}, metadata: { vulnerabilities: { info: 0, low: 0, moderate: 0, high: 0, critical: 0, total: 0 } } }), 'licenses.json': JSON.stringify(overrides.licenses ?? { 'portfolio@1.0.0': { licenses: 'ISC' } }), 'sbom.cdx.json': JSON.stringify(overrides.sbom ?? { bomFormat: 'CycloneDX', specVersion: '1.6', version: 1, metadata: { component: { type: 'application', name: 'portfolio', version: '1.0.0' } }, components: [] }), 'medium-triage.json': JSON.stringify(overrides.triage ?? { schema: 'portfolio-medium-vulnerability-triage/v1', findings: [] }), 'execution-security.json': JSON.stringify(executionEnvelope), }; for (const [name, contents] of Object.entries(reports)) await writeFile(join(evidence, name), contents); const manifest = { schema: 'portfolio-supply-chain-evidence/v1', packageJsonSha256: sha256(packageJson), packageLockSha256: sha256(packageLock), reports: Object.fromEntries(Object.entries(reports).map(([name, contents]) => [name, sha256(contents)])), toolVersions: { npm: '11.0.0', cyclonedxNpm: '6.0.1', licenseChecker: '4.4.2' }, }; await writeFile(join(evidence, 'evidence-manifest.json'), JSON.stringify(manifest)); return { root, evidence, trustPolicyPath }; } const verifyFixture = (fixture: Awaited>, extra: Record = {}) => verifySupplyChainEvidence({ projectRoot: fixture.root, evidenceDir: fixture.evidence, toolTrustPolicyPath: fixture.trustPolicyPath, requireTrackedTriage: false, ...extra, }); describe('supply-chain evidence', () => { it('accepts a digest-bound clean audit, allowlisted licenses, and CycloneDX SBOM', async () => { const fixture = await evidenceFixture(); await expect(verifyFixture(fixture, { now: '2026-09-02T12:00:00Z' })).resolves.toMatchObject({ critical: 0, high: 0, medium: 0, componentCount: 0, executionImages: 4, }); }); it('fails closed on altered evidence, Critical/High findings, or forbidden licenses', async () => { const fixture = await evidenceFixture(); await writeFile(join(fixture.evidence, 'licenses.json'), JSON.stringify({ 'portfolio@1.0.0': { licenses: 'AGPL-3.0' } })); await expect(verifyFixture(fixture)).rejects.toThrow(/digest/u); const high = await evidenceFixture({ productionAudit: { auditReportVersion: 2, vulnerabilities: { unsafe: { name: 'unsafe', severity: 'high', via: [], effects: [], range: '*', nodes: [] } }, metadata: { vulnerabilities: { info: 0, low: 0, moderate: 0, high: 1, critical: 0, total: 1 } } }, }); await expect(verifyFixture(high)).rejects.toThrow(/High|high/u); const license = await evidenceFixture({ licenses: { 'portfolio@1.0.0': { licenses: 'AGPL-3.0-only' } } }); await expect(verifyFixture(license)).rejects.toThrow(/license/u); }); it('requires an exact, current triage record for every Medium finding', async () => { const audit = { auditReportVersion: 2, vulnerabilities: { packageA: { name: 'packageA', severity: 'moderate', via: [], effects: [], range: '<2', nodes: ['node_modules/packageA'] } }, metadata: { vulnerabilities: { info: 0, low: 0, moderate: 1, high: 0, critical: 0, total: 1 } } }; const missing = await evidenceFixture({ developmentAudit: audit }); await expect(verifyFixture(missing)).rejects.toThrow(/triage/u); const accepted = await evidenceFixture({ developmentAudit: audit, triage: { schema: 'portfolio-medium-vulnerability-triage/v1', findings: [{ scope: 'development', package: 'packageA', severity: 'moderate', range: '<2', disposition: 'accepted', rationale: 'Build-only tool; no deployed runtime exposure.', owner: 'Luca Pîslaru', reviewBy: '2027-01-01' }] }, }); await expect(verifyFixture(accepted, { today: '2026-09-02' })).resolves.toMatchObject({ medium: 1 }); }); it('fails closed on unsigned tooling, vulnerable images, or incomplete repository security evidence', async () => { const unsigned = await evidenceFixture({ executionEnvelope: { schema: 'portfolio-execution-security-evidence-envelope/v1', payload: {}, signature: { algorithm: 'Ed25519', encoding: 'base64url-nopad', value: '' } } }); await expect(verifyFixture(unsigned)).rejects.toThrow(/execution|signature|payload/u); const vulnerable = await evidenceFixture({ executionPayload: { schema: 'portfolio-execution-security-evidence/v1', keyId: 'supply-chain-test-key', issuerEpoch: 1, scope: 'portfolio-execution-security-evidence/v1', generatedAt: '2026-09-02T00:00:00Z', expiresAt: '2026-09-03T00:00:00Z', images: ['coordinator', 'matcher', 'worker-a', 'worker-b'].map((role, index) => ({ role, image: `registry.invalid/${role}@sha256:${String(index + 1).repeat(64)}`, provenanceVerified: true, critical: 0, high: role === 'worker-a' ? 1 : 0 })), nativeTools: [{ name: 'nginx', version: '1.29.1', sha256: 'a'.repeat(64), signatureVerified: true, critical: 0, high: 0 }], repository: { commitSha: 'b'.repeat(40), secretScanPassed: true, historyScanPassed: true, ciActionsPinned: true, distributionNoticesComplete: true }, } }); await expect(verifyFixture(vulnerable)).rejects.toThrow(/Critical|High|vulnerab/u); const unscanned = await evidenceFixture({ executionPayload: { schema: 'portfolio-execution-security-evidence/v1', keyId: 'supply-chain-test-key', issuerEpoch: 1, scope: 'portfolio-execution-security-evidence/v1', generatedAt: '2026-09-02T00:00:00Z', expiresAt: '2026-09-03T00:00:00Z', images: ['coordinator', 'matcher', 'worker-a', 'worker-b'].map((role, index) => ({ role, image: `registry.invalid/${role}@sha256:${String(index + 1).repeat(64)}`, provenanceVerified: true, critical: 0, high: 0 })), nativeTools: [{ name: 'nginx', version: '1.29.1', sha256: 'a'.repeat(64), signatureVerified: true, critical: 0, high: 0 }], repository: { commitSha: 'b'.repeat(40), secretScanPassed: false, historyScanPassed: true, ciActionsPinned: true, distributionNoticesComplete: true }, } }); await expect(verifyFixture(unscanned)).rejects.toThrow(/repository|secret/u); }); });