diff --git a/test/openclaw-integrity-pin-suite.ts b/test/openclaw-integrity-pin-suite.ts index 69ceeaf77c2..4b7111709e4 100644 --- a/test/openclaw-integrity-pin-suite.ts +++ b/test/openclaw-integrity-pin-suite.ts @@ -7,6 +7,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { parseAuditExceptionRegistry } from "../scripts/lib/reviewed-npm-audit.mts"; import { createBuiltInChannelManifestRegistry } from "../src/lib/messaging"; import { reviewedOpenClawPluginIntegrityByPackageSpec } from "../src/lib/messaging/applier/build/messaging-build-applier.mts"; @@ -77,12 +78,20 @@ const MCPORTER_LOCKFILE = path.join( "package-lock.json", ); const NPM_AUDIT_EXCEPTION_FILE = path.join(REPO_ROOT, "ci", "npm-audit-exceptions.json"); +const NPM_AUDIT_EXCEPTION_POLICY = fs.readFileSync(NPM_AUDIT_EXCEPTION_FILE, "utf-8"); const PINNED_MCPORTER_LOCK_SHA256 = createHash("sha256") .update(fs.readFileSync(MCPORTER_LOCKFILE)) .digest("hex"); const NPM_AUDIT_EXCEPTION_POLICY_SHA256 = createHash("sha256") - .update(fs.readFileSync(NPM_AUDIT_EXCEPTION_FILE)) + .update(NPM_AUDIT_EXCEPTION_POLICY) .digest("hex"); +const MCPORTER_AUDIT_EXCEPTIONS = parseAuditExceptionRegistry(NPM_AUDIT_EXCEPTION_POLICY) + .exceptions.filter((entry) => entry.graph === "mcporter-runtime") + .map((entry) => entry.advisory) + .sort(); +const MCPORTER_AUDIT_EXCEPTION_LIST = MCPORTER_AUDIT_EXCEPTIONS.join(",") || "none"; +const MCPORTER_AUDIT_STATUS = + MCPORTER_AUDIT_EXCEPTIONS.length > 0 ? "accepted-exceptions" : "clean"; const PINNED_OPENCLAW_DIAGNOSTICS_OTEL_INTEGRITY = "sha512-XXhMifYWTgoR6yFN4T3JkHxdPvQCe8k1cNZjVIgXNmk1svCdBWuALfQQicmpemlmWwauIQuHYgBURY6k63e+rw=="; const PINNED_OPENCLAW_DIAGNOSTICS_OTEL_TARBALL = @@ -122,9 +131,9 @@ function openClawBaseProvenance( sha256: string; status: "accepted-exceptions" | "clean"; }> = { - exceptions: "none", + exceptions: MCPORTER_AUDIT_EXCEPTION_LIST, sha256: NPM_AUDIT_EXCEPTION_POLICY_SHA256, - status: "clean", + status: MCPORTER_AUDIT_STATUS, }, ): string { const lockSha256 = @@ -247,6 +256,16 @@ function runInstallBlock( const auditExceptionPolicySha256 = createHash("sha256") .update(auditExceptionPolicy) .digest("hex"); + const auditExceptionsByGraph: Record = {}; + for (const entry of ( + JSON.parse(auditExceptionPolicy) as { + exceptions?: Array<{ advisory?: string; graph?: string }>; + } + ).exceptions ?? []) { + if (!entry.advisory || !entry.graph) continue; + (auditExceptionsByGraph[entry.graph] ??= []).push(entry.advisory); + } + for (const advisories of Object.values(auditExceptionsByGraph)) advisories.sort(); fs.mkdirSync(path.dirname(mcporterBin), { recursive: true }); fs.mkdirSync(openclawRuntime, { recursive: true }); fs.mkdirSync(mcporterRuntime, { recursive: true }); @@ -313,7 +332,10 @@ function runInstallBlock( "const value = (name) => args[args.indexOf(name) + 1];", "const counts = { info: 0, low: 0, moderate: 0, high: 0, critical: 0 };", "const report = { auditReportVersion: 2, vulnerabilities: {}, metadata: { vulnerabilities: counts } };", - `const policy = { schemaVersion: 1, graph: value("--graph"), blockingThreshold: value("--threshold"), exceptionPolicySha256: ${JSON.stringify(auditExceptionPolicySha256)}, reported: counts, status: "clean", acceptedAdvisories: [], unacceptedBlockingAdvisories: [] };`, + `const acceptedByGraph = ${JSON.stringify(auditExceptionsByGraph)};`, + 'const graph = value("--graph");', + "const acceptedAdvisories = acceptedByGraph[graph] ?? [];", + `const policy = { schemaVersion: 1, graph, blockingThreshold: value("--threshold"), exceptionPolicySha256: ${JSON.stringify(auditExceptionPolicySha256)}, reported: counts, status: acceptedAdvisories.length > 0 ? "accepted-exceptions" : "clean", acceptedAdvisories, unacceptedBlockingAdvisories: [] };`, 'if (args.includes("--report")) fs.writeFileSync(value("--report"), `${JSON.stringify(report)}\\n`);', 'if (args.includes("--result")) fs.writeFileSync(value("--result"), `${JSON.stringify(policy)}\\n`);', "console.log(`npm audit policy ${policy.graph}: clean`);", @@ -1170,8 +1192,10 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "wrong mcporter audit status", { baseProvenance: openClawBaseProvenance().replace( - "mcporter-audit-status=clean", - "mcporter-audit-status=accepted-exceptions", + `mcporter-audit-status=${MCPORTER_AUDIT_STATUS}`, + `mcporter-audit-status=${ + MCPORTER_AUDIT_STATUS === "clean" ? "accepted-exceptions" : "clean" + }`, ), }, ], @@ -1179,7 +1203,7 @@ export function registerOpenClawIntegrityPinTests(group: OpenClawIntegrityPinTes "wrong mcporter audit exceptions", { baseProvenance: openClawBaseProvenance().replace( - "mcporter-audit-exceptions=none", + `mcporter-audit-exceptions=${MCPORTER_AUDIT_EXCEPTION_LIST}`, "mcporter-audit-exceptions=GHSA-aaaa-bbbb-cccc", ), }, diff --git a/test/reviewed-npm-audit.test.ts b/test/reviewed-npm-audit.test.ts index 0f6d00da94b..4cf14d4d230 100644 --- a/test/reviewed-npm-audit.test.ts +++ b/test/reviewed-npm-audit.test.ts @@ -28,9 +28,10 @@ const CONFIG = JSON.parse( ) as { severityThreshold: "info" | "low" | "moderate" | "high" | "critical"; }; -const EMPTY_POLICY = parseAuditExceptionRegistry( +const CHECKED_IN_POLICY = parseAuditExceptionRegistry( fs.readFileSync(path.join(REPO_ROOT, "ci", "npm-audit-exceptions.json"), "utf-8"), ); +const EMPTY_POLICY: AuditExceptionRegistry = { schemaVersion: 1, exceptions: [] }; const NOW = new Date("2026-07-21T12:00:00Z"); function withInstalledGraph( @@ -119,7 +120,7 @@ function exceptionPolicy( describe("reviewed npm audit gate", () => { it("uses an empty exception registry by default", () => { - expect(EMPTY_POLICY).toEqual({ schemaVersion: 1, exceptions: [] }); + expect(CHECKED_IN_POLICY).toEqual(EMPTY_POLICY); }); it("fails at high or critical findings while retaining lower severities", () => {