From cf5c15dee8ae8aa01e30cea5d27075c7f4fdf3ba Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 19 Aug 2026 03:11:37 -0400 Subject: [PATCH 1/4] ci: enforce scoped production audit policy --- .github/production-audit-policy.json | 43 ++++ .github/workflows/ci.yml | 33 ++- .github/workflows/release.yml | 25 +- package.json | 2 + scripts/audit-production-policy.ts | 338 +++++++++++++++++++++++++++ scripts/audit-production.sh | 5 + scripts/audit-production.test.ts | 174 ++++++++++++++ scripts/audit-production.ts | 115 +++++++++ 8 files changed, 723 insertions(+), 12 deletions(-) create mode 100644 .github/production-audit-policy.json create mode 100644 scripts/audit-production-policy.ts create mode 100644 scripts/audit-production.sh create mode 100644 scripts/audit-production.test.ts create mode 100644 scripts/audit-production.ts diff --git a/.github/production-audit-policy.json b/.github/production-audit-policy.json new file mode 100644 index 000000000..60216800f --- /dev/null +++ b/.github/production-audit-policy.json @@ -0,0 +1,43 @@ +{ + "auditLevel": "high", + "scope": "Production dependencies declared by each published package, resolved in isolated consumer manifests without monorepo overrides", + "acceptedAdvisories": [ + { + "id": "GHSA-xcpc-8h2w-3j85", + "package": "adm-zip", + "severity": "high", + "affectedArtifacts": [ + "@cortexkit/opencode-magic-context", + "@cortexkit/pi-magic-context" + ], + "dependencyPath": "@huggingface/transformers@4.2.0 > onnxruntime-node@1.24.3 > adm-zip@0.5.18", + "reachability": "onnxruntime-node uses adm-zip only during native-provider installation; runtime inference does not parse ZIP input", + "owner": "cortexkit/magic-context maintainers", + "rationale": "No released onnxruntime-node version permits patched adm-zip@0.6.x; acceptance is temporary pending the upstream range change and coordinated Transformers release", + "expires": "2026-11-17", + "upstream": [ + "https://github.com/huggingface/transformers.js/issues/1727", + "https://github.com/microsoft/onnxruntime/pull/29772", + "https://github.com/huggingface/transformers.js/pull/1731" + ] + }, + { + "id": "GHSA-f88m-g3jw-g9cj", + "package": "sharp", + "severity": "high", + "affectedArtifacts": [ + "@cortexkit/opencode-magic-context", + "@cortexkit/pi-magic-context" + ], + "dependencyPath": "@huggingface/transformers@4.2.0 > sharp@0.34.5", + "reachability": "Magic Context uses the Transformers text-embedding pipeline and does not pass image input to sharp", + "owner": "cortexkit/magic-context maintainers", + "rationale": "Transformers' released ^0.34.5 range cannot select patched sharp@0.35.x; acceptance is temporary pending the upstream range change and release", + "expires": "2026-11-17", + "upstream": [ + "https://github.com/huggingface/transformers.js/issues/1729", + "https://github.com/huggingface/transformers.js/pull/1731" + ] + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7be3b60c0..1eb081878 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + BUN_VERSION: "1.3.10" # Pipeline shape on every push to master and on every PR: # @@ -28,6 +29,22 @@ env: # every PR and master push exercises the full unit → Docker → host gauntlet. jobs: + audit-production: + name: Audit (published production dependencies) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ env.BUN_VERSION }} + + - name: Test production audit policy + run: bun run test:audit-production + + - name: Reject unaccepted HIGH production advisories + run: bun run audit:production + check-plugin: name: Check (plugin) runs-on: ubuntu-latest @@ -36,7 +53,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: ${{ env.BUN_VERSION }} # node:sqlite (the Pi / OpenCode-Desktop SQLite backend) is only reachable # under Node, not Bun. Node 24 also strips the smoke script's inline TS. @@ -104,7 +121,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: ${{ env.BUN_VERSION }} - name: Install dependencies run: bun install --frozen-lockfile @@ -129,7 +146,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: ${{ env.BUN_VERSION }} - name: Install dependencies run: bun install --frozen-lockfile @@ -160,7 +177,7 @@ jobs: - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: ${{ env.BUN_VERSION }} - name: Install workspace deps run: bun install --frozen-lockfile @@ -193,7 +210,7 @@ jobs: - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: ${{ env.BUN_VERSION }} - name: Install workspace deps run: bun install --frozen-lockfile @@ -230,7 +247,7 @@ jobs: - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: ${{ env.BUN_VERSION }} - name: Install workspace deps run: bun install --frozen-lockfile @@ -266,7 +283,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: ${{ env.BUN_VERSION }} - name: Install workspace deps run: bun install --frozen-lockfile @@ -328,7 +345,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: ${{ env.BUN_VERSION }} # Pi tests resolve the Pi binary via createRequire against # @earendil-works/pi-coding-agent, which is a workspace dep of diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 017701c1f..331cf58fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,10 +11,11 @@ permissions: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + BUN_VERSION: "1.3.10" # Pipeline shape (each stage gates the next via `needs:`): # -# test, test-pi, test-cli (unit tests, parallel) +# audit-production, test, test-pi, test-cli (policy/audit + unit tests, parallel) # ↓ # e2e-opencode, e2e-pi (Docker install + smoke, parallel — gated by unit tests) # ↓ @@ -47,6 +48,22 @@ env: # (workflow_dispatch only) for re-announcements and external releases. jobs: + audit-production: + name: Audit (published production dependencies) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: ${{ env.BUN_VERSION }} + + - name: Test production audit policy + run: bun run test:audit-production + + - name: Reject unaccepted HIGH production advisories + run: bun run audit:production + test: name: Test (plugin) runs-on: ubuntu-latest @@ -329,7 +346,7 @@ jobs: # Publishes are gated on the full e2e suite (OpenCode + Pi) so a tag # push can never produce a published npm package whose runtime fails # the install + first-turn smoke test. - needs: [test, test-pi, test-cli, e2e-opencode, e2e-pi, e2e-host-opencode, e2e-host-pi] + needs: [audit-production, test, test-pi, test-cli, e2e-opencode, e2e-pi, e2e-host-opencode, e2e-host-pi] steps: - uses: actions/checkout@v5 @@ -368,7 +385,7 @@ jobs: publish-npm-pi: name: Publish pi-plugin to npm runs-on: ubuntu-latest - needs: [test, test-pi, test-cli, e2e-opencode, e2e-pi, e2e-host-opencode, e2e-host-pi] + needs: [audit-production, test, test-pi, test-cli, e2e-opencode, e2e-pi, e2e-host-opencode, e2e-host-pi] steps: - uses: actions/checkout@v5 @@ -402,7 +419,7 @@ jobs: publish-npm-cli: name: Publish unified CLI to npm runs-on: ubuntu-latest - needs: [test, test-pi, test-cli, e2e-opencode, e2e-pi, e2e-host-opencode, e2e-host-pi] + needs: [audit-production, test, test-pi, test-cli, e2e-opencode, e2e-pi, e2e-host-opencode, e2e-host-pi] steps: - uses: actions/checkout@v5 diff --git a/package.json b/package.json index c44c34abd..d1453d463 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "test:rust": "cargo test --workspace", "lint:rust": "cargo fmt --check && cargo clippy --workspace --all-targets -- -D warnings", "fmt:rust": "cargo fmt", + "audit:production": "bash scripts/audit-production.sh", + "test:audit-production": "bun test scripts/audit-production.test.ts", "check:all": "bun run test && cargo test --workspace && cargo clippy --workspace --all-targets -- -D warnings", "build:dists": "bun run --cwd packages/plugin build && bun run --cwd packages/pi-plugin build" } diff --git a/scripts/audit-production-policy.ts b/scripts/audit-production-policy.ts new file mode 100644 index 000000000..92ba60e69 --- /dev/null +++ b/scripts/audit-production-policy.ts @@ -0,0 +1,338 @@ +type AcceptedAdvisory = { + readonly id: string; + readonly package: string; + readonly severity: "high" | "critical"; + readonly affectedArtifacts: readonly string[]; + readonly dependencyPath: string; + readonly reachability: string; + readonly owner: string; + readonly rationale: string; + readonly expires: string; + readonly upstream: readonly string[]; +}; + +export type AuditPolicy = { + readonly auditLevel: "high"; + readonly scope: string; + readonly acceptedAdvisories: readonly AcceptedAdvisory[]; +}; + +export type ProductionManifest = { + readonly name: string; + readonly version: string; + readonly private: true; + readonly [key: string]: unknown; +}; + +type Finding = { + readonly id: string; + readonly package: string; + readonly severity: "high" | "critical"; + readonly title: string; + readonly dependencyPath: string; +}; + +type AcceptedFinding = Finding & { readonly exception: AcceptedAdvisory }; + +type Evaluation = { + readonly accepted: readonly AcceptedFinding[]; + readonly rejected: readonly Finding[]; +}; + +export class AuditInputError extends Error {} + +function isRecord(value: unknown): value is Readonly> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function requiredString(value: unknown, field: string): string { + if ( + typeof value !== "string" || + value.trim() !== value || + value.length === 0 + ) { + throw new AuditInputError(`${field} must be a non-empty trimmed string`); + } + return value; +} + +function stringArray(value: unknown, field: string): readonly string[] { + if (!Array.isArray(value) || value.length === 0) { + throw new AuditInputError(`${field} must be a non-empty string array`); + } + return value.map((item, index) => requiredString(item, `${field}[${index}]`)); +} + +function isPackageName(value: string): boolean { + return /^(?:@[a-z0-9._~-]+\/)?[a-z0-9._~-]+$/i.test(value); +} + +function parseResolvedNode(value: string, field: string): string { + const separator = value.lastIndexOf("@"); + const packageName = value.slice(0, separator); + const version = value.slice(separator + 1); + if ( + separator < 1 || + !isPackageName(packageName) || + !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version) + ) { + throw new AuditInputError( + `${field} must contain exact package@version nodes`, + ); + } + return packageName; +} + +function isoDate(value: unknown, field: string): string { + const date = requiredString(value, field); + const parsed = new Date(`${date}T00:00:00.000Z`); + if ( + !/^\d{4}-\d{2}-\d{2}$/.test(date) || + Number.isNaN(parsed.valueOf()) || + parsed.toISOString().slice(0, 10) !== date + ) { + throw new AuditInputError(`${field} must be a real YYYY-MM-DD date`); + } + return date; +} + +function httpsUrl(value: string, field: string): string { + let parsed: URL; + try { + parsed = new URL(value); + } catch (error) { + if (error instanceof TypeError) + throw new AuditInputError(`${field} must be a valid HTTPS URL`); + throw error; + } + if ( + parsed.protocol !== "https:" || + parsed.username !== "" || + parsed.password !== "" || + parsed.hostname === "" + ) { + throw new AuditInputError(`${field} must be a credential-free HTTPS URL`); + } + return value; +} + +function parseAcceptedAdvisory( + value: unknown, + index: number, + today: string, +): AcceptedAdvisory { + if (!isRecord(value)) + throw new AuditInputError(`acceptedAdvisories[${index}] must be an object`); + const field = (name: string): string => + `acceptedAdvisories[${index}].${name}`; + const id = requiredString(value.id, field("id")); + if ( + !/^(?:GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}|CVE-\d{4}-\d{4,})$/i.test(id) + ) { + throw new AuditInputError(`${field("id")} must be a GHSA or CVE ID`); + } + const packageName = requiredString(value.package, field("package")); + if (!isPackageName(packageName)) + throw new AuditInputError(`${field("package")} is invalid`); + if (value.severity !== "high" && value.severity !== "critical") { + throw new AuditInputError(`${field("severity")} must be high or critical`); + } + const affectedArtifacts = stringArray( + value.affectedArtifacts, + field("affectedArtifacts"), + ); + if (affectedArtifacts.some((artifact) => !isPackageName(artifact))) { + throw new AuditInputError( + `${field("affectedArtifacts")} contains an invalid package name`, + ); + } + const dependencyPath = requiredString( + value.dependencyPath, + field("dependencyPath"), + ); + const pathPackages = dependencyPath + .split(" > ") + .map((node, nodeIndex) => + parseResolvedNode(node, `${field("dependencyPath")}[${nodeIndex}]`), + ); + if (pathPackages.at(-1) !== packageName) { + throw new AuditInputError( + `${field("dependencyPath")} must end with ${packageName}@version`, + ); + } + const expires = isoDate(value.expires, field("expires")); + if (expires <= today) + throw new AuditInputError(`${id} acceptance expired on ${expires}`); + const upstream = stringArray(value.upstream, field("upstream")).map( + (url, urlIndex) => httpsUrl(url, `${field("upstream")}[${urlIndex}]`), + ); + return { + id, + package: packageName, + severity: value.severity, + affectedArtifacts, + dependencyPath, + reachability: requiredString(value.reachability, field("reachability")), + owner: requiredString(value.owner, field("owner")), + rationale: requiredString(value.rationale, field("rationale")), + expires, + upstream, + }; +} + +export function parsePolicy(value: unknown, today: string): AuditPolicy { + if (!isRecord(value)) + throw new AuditInputError("production audit policy must be an object"); + if (value.auditLevel !== "high") + throw new AuditInputError( + "production audit policy auditLevel must be high", + ); + if (!Array.isArray(value.acceptedAdvisories)) { + throw new AuditInputError( + "production audit policy acceptedAdvisories must be an array", + ); + } + isoDate(today, "current date"); + return { + auditLevel: "high", + scope: requiredString(value.scope, "production audit policy scope"), + acceptedAdvisories: value.acceptedAdvisories.map((entry, index) => + parseAcceptedAdvisory(entry, index, today), + ), + }; +} + +function dependencyGroup( + value: unknown, + field: string, +): Readonly> | undefined { + if (value === undefined) return undefined; + if (!isRecord(value)) throw new AuditInputError(`${field} must be an object`); + return value; +} + +export function createProductionManifest(value: unknown): ProductionManifest { + if (!isRecord(value)) + throw new AuditInputError("package manifest must be an object"); + const manifest: Record & ProductionManifest = { + name: requiredString(value.name, "package name"), + version: requiredString(value.version, "package version"), + private: true, + }; + for (const group of [ + "dependencies", + "optionalDependencies", + "peerDependencies", + "peerDependenciesMeta", + ] as const) { + const dependencies = dependencyGroup(value[group], group); + if (dependencies !== undefined) manifest[group] = dependencies; + } + return manifest; +} + +export function resolvedDependencyPaths( + output: string, + artifact: string, +): readonly string[] { + const stack: string[] = []; + const paths = new Set(); + for (const line of output.split("\n")) { + if (line.trim() === "") continue; + const match = /^(\s*)(?:[├└]─\s)?(.+?)(?:\s+\(requires .+\))?$/.exec(line); + if (match === null) + throw new AuditInputError(`unrecognized bun pm why line: ${line}`); + const indentation = match[1]?.length ?? 0; + const node = match[2]; + if (node === undefined) + throw new AuditInputError(`missing package in bun pm why line: ${line}`); + const depth = indentation === 0 ? 0 : (indentation + 1) / 3; + if (!Number.isInteger(depth)) + throw new AuditInputError(`unrecognized bun pm why indentation: ${line}`); + stack[depth] = node; + stack.length = depth + 1; + const rootNode = node.replace(/^(?:optional|peer)\s+/, ""); + if (rootNode === artifact || rootNode.startsWith(`${artifact}@`)) { + paths.add(stack.slice(0, -1).reverse().join(" > ")); + } + } + return [...paths]; +} + +function auditEntries(value: unknown): readonly [string, readonly unknown[]][] { + if (!isRecord(value)) + throw new AuditInputError("bun audit JSON must be an object"); + return Object.entries(value).map(([packageName, advisories]) => { + if (!isPackageName(packageName) || !Array.isArray(advisories)) { + throw new AuditInputError( + "bun audit JSON contains an invalid package entry", + ); + } + return [packageName, advisories] as const; + }); +} + +export function auditPackageNames(value: unknown): readonly string[] { + return auditEntries(value).map(([packageName]) => packageName); +} + +function parseFinding( + value: unknown, + packageName: string, + dependencyPath: string, +): Finding | undefined { + if (!isRecord(value)) + throw new AuditInputError( + `bun audit advisory for ${packageName} must be an object`, + ); + if (value.severity !== "high" && value.severity !== "critical") + return undefined; + const url = httpsUrl( + requiredString(value.url, `${packageName} advisory URL`), + `${packageName} advisory URL`, + ); + const id = new URL(url).pathname.split("/").at(-1); + if (id === undefined || id === "") + throw new AuditInputError(`${packageName} advisory URL has no ID`); + return { + id, + package: packageName, + severity: value.severity, + title: requiredString(value.title, `${packageName} advisory title`), + dependencyPath, + }; +} + +export function evaluateAudit( + audit: unknown, + policy: AuditPolicy, + artifact: string, + pathsByPackage: Readonly>, +): Evaluation { + const accepted: AcceptedFinding[] = []; + const rejected: Finding[] = []; + for (const [packageName, advisories] of auditEntries(audit)) { + const resolvedPaths = pathsByPackage[packageName]; + const paths = + resolvedPaths === undefined || resolvedPaths.length === 0 + ? [""] + : resolvedPaths; + for (const advisory of advisories) { + for (const dependencyPath of paths) { + const finding = parseFinding(advisory, packageName, dependencyPath); + if (finding === undefined) continue; + const exception = policy.acceptedAdvisories.find( + (candidate) => + candidate.id === finding.id && + candidate.package === finding.package && + candidate.severity === finding.severity && + candidate.affectedArtifacts.includes(artifact) && + candidate.dependencyPath === finding.dependencyPath, + ); + if (exception === undefined) rejected.push(finding); + else accepted.push({ ...finding, exception }); + } + } + } + return { accepted, rejected }; +} diff --git a/scripts/audit-production.sh b/scripts/audit-production.sh new file mode 100644 index 000000000..d2434fe78 --- /dev/null +++ b/scripts/audit-production.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +exec bun "$root/scripts/audit-production.ts" "$@" diff --git a/scripts/audit-production.test.ts b/scripts/audit-production.test.ts new file mode 100644 index 000000000..c3357c65d --- /dev/null +++ b/scripts/audit-production.test.ts @@ -0,0 +1,174 @@ +import { describe, expect, test } from "bun:test"; +import { + createProductionManifest, + evaluateAudit, + parsePolicy, + resolvedDependencyPaths, +} from "./audit-production-policy"; + +const artifact = "@example/artifact"; +const advisoryId = "GHSA-aaaa-bbbb-cccc"; +const dependencyPath = "parent@1.0.0 > vulnerable@1.0.0"; + +function policy(overrides: Record = {}): unknown { + return { + auditLevel: "high", + scope: "Published production dependencies", + acceptedAdvisories: [ + { + id: advisoryId, + package: "vulnerable", + severity: "high", + affectedArtifacts: [artifact], + dependencyPath, + reachability: "The vulnerable parser is unreachable.", + owner: "security@example.com", + rationale: "Blocked on an upstream release.", + expires: "2026-11-17", + upstream: ["https://github.com/example/upstream/issues/1"], + ...overrides, + }, + ], + }; +} + +const audit = { + vulnerable: [ + { + url: `https://github.com/advisories/${advisoryId}`, + title: "Synthetic HIGH advisory", + severity: "high", + }, + ], +}; + +describe("production audit policy", () => { + test("accepts a finding only when ID, package, artifact, and resolved path match", () => { + // Given + const parsed = parsePolicy(policy(), "2026-08-19"); + + // When + const result = evaluateAudit(audit, parsed, artifact, { + vulnerable: [dependencyPath], + }); + + // Then + expect(result.accepted).toHaveLength(1); + expect(result.rejected).toHaveLength(0); + }); + + test.each([ + ["wrong artifact", "@example/other", dependencyPath], + ["wrong path", artifact, "other-parent@1.0.0 > vulnerable@1.0.0"], + ])("rejects an accepted advisory on the %s", (_case, actualArtifact, actualPath) => { + // Given + const parsed = parsePolicy(policy(), "2026-08-19"); + + // When + const result = evaluateAudit(audit, parsed, actualArtifact, { + vulnerable: [actualPath], + }); + + // Then + expect(result.rejected).toHaveLength(1); + }); + + test("rejects a new HIGH advisory", () => { + // Given + const parsed = parsePolicy(policy(), "2026-08-19"); + const newAudit = { + other: [ + { + url: "https://github.com/advisories/GHSA-dddd-eeee-ffff", + title: "New HIGH advisory", + severity: "high", + }, + ], + }; + + // When + const result = evaluateAudit(newAudit, parsed, artifact, { + other: ["other@1.0.0"], + }); + + // Then + expect(result.rejected).toHaveLength(1); + }); + + test.each([ + "2026-99-99", + "17-11-2026", + "not-a-date", + ])("rejects malformed expiry %s", (expires) => { + // Given / When / Then + expect(() => parsePolicy(policy({ expires }), "2026-08-19")).toThrow(); + }); + + test.each([ + "not-a-url", + "http://github.com/example/upstream/issues/1", + "https://", + ])("rejects malformed or insecure upstream URL %s", (upstream) => { + // Given / When / Then + expect(() => + parsePolicy(policy({ upstream: [upstream] }), "2026-08-19"), + ).toThrow(); + }); +}); + +test("production manifest preserves every production dependency group", () => { + // Given + const source = { + name: artifact, + version: "1.0.0", + dependencies: { runtime: "1.0.0" }, + optionalDependencies: { optional: "1.0.0" }, + peerDependencies: { peer: "1.0.0" }, + peerDependenciesMeta: { peer: { optional: false } }, + devDependencies: { devOnly: "1.0.0" }, + }; + + // When + const manifest = createProductionManifest(source); + + // Then + expect(manifest).toEqual({ + name: artifact, + version: "1.0.0", + private: true, + dependencies: source.dependencies, + optionalDependencies: source.optionalDependencies, + peerDependencies: source.peerDependencies, + peerDependenciesMeta: source.peerDependenciesMeta, + }); +}); + +test("resolved paths reverse Bun why output and exclude the artifact root", () => { + // Given + const why = `vulnerable@1.0.0 + └─ parent@1.0.0 (requires ^1.0.0) + └─ @example/artifact (requires ^1.0.0) +`; + + // When + const paths = resolvedDependencyPaths(why, artifact); + + // Then + expect(paths).toEqual([dependencyPath]); +}); + +test.each([ + "optional", + "peer", +])("resolved paths recognize Bun's %s root marker", (relationship) => { + // Given + const why = `vulnerable@1.0.0 + └─ ${relationship} @example/artifact (requires 1.0.0) +`; + + // When + const paths = resolvedDependencyPaths(why, artifact); + + // Then + expect(paths).toEqual(["vulnerable@1.0.0"]); +}); diff --git a/scripts/audit-production.ts b/scripts/audit-production.ts new file mode 100644 index 000000000..3b7635092 --- /dev/null +++ b/scripts/audit-production.ts @@ -0,0 +1,115 @@ +#!/usr/bin/env bun +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { isAbsolute, join, resolve } from "node:path"; +import { + AuditInputError, + auditPackageNames, + createProductionManifest, + evaluateAudit, + parsePolicy, + resolvedDependencyPaths, +} from "./audit-production-policy"; + +function run( + command: readonly string[], + cwd: string, + acceptedExitCodes: readonly number[] = [0], +): string { + const result = Bun.spawnSync([...command], { + cwd, + stdout: "pipe", + stderr: "inherit", + }); + if (!acceptedExitCodes.includes(result.exitCode)) { + throw new AuditInputError( + `${command.join(" ")} failed with exit code ${result.exitCode}`, + ); + } + return new TextDecoder().decode(result.stdout).trim(); +} + +async function main(): Promise { + const root = resolve(import.meta.dir, ".."); + const policy = parsePolicy( + JSON.parse( + await readFile( + join(root, ".github/production-audit-policy.json"), + "utf8", + ), + ), + new Date().toISOString().slice(0, 10), + ); + const packageArgs = process.argv.slice(2); + const packages = + packageArgs.length === 0 + ? ["packages/plugin", "packages/pi-plugin", "packages/cli"] + : packageArgs; + const temporaryRoot = await mkdtemp( + join(tmpdir(), "magic-context-production-audit-"), + ); + let rejected = 0; + try { + for (const [index, packageArg] of packages.entries()) { + const packageDirectory = isAbsolute(packageArg) + ? packageArg + : join(root, packageArg); + const source = JSON.parse( + await readFile(join(packageDirectory, "package.json"), "utf8"), + ); + const manifest = createProductionManifest(source); + const artifact = manifest.name; + const consumer = join(temporaryRoot, String(index)); + await Bun.write( + join(consumer, "package.json"), + JSON.stringify(manifest, null, 2), + ); + console.log(`Auditing production dependencies for ${artifact}`); + run( + [ + process.execPath, + "install", + "--lockfile-only", + "--save-text-lockfile", + "--silent", + ], + consumer, + ); + const auditOutput = run( + [process.execPath, "audit", "--audit-level=high", "--json"], + consumer, + [0, 1], + ); + const audit: unknown = JSON.parse(auditOutput); + const pathsByPackage = Object.fromEntries( + auditPackageNames(audit).map((packageName) => [ + packageName, + resolvedDependencyPaths( + run([process.execPath, "pm", "why", packageName], consumer), + artifact, + ), + ]), + ); + const evaluation = evaluateAudit(audit, policy, artifact, pathsByPackage); + for (const finding of evaluation.accepted) { + console.log( + `Accepted ${finding.id} in ${finding.package} for ${artifact} via ${finding.dependencyPath}; expires ${finding.exception.expires}`, + ); + } + for (const finding of evaluation.rejected) { + console.error( + `Unaccepted ${finding.severity.toUpperCase()} ${finding.id} in ${finding.package} for ${artifact} via ${finding.dependencyPath}`, + ); + } + rejected += evaluation.rejected.length; + } + } finally { + await rm(temporaryRoot, { recursive: true, force: true }); + } + if (rejected > 0) + throw new AuditInputError( + `${rejected} unaccepted HIGH/CRITICAL production advisory path(s)`, + ); +} + +await main(); From 73ee136f7eed102f51df698bdb31bf1ed62f54e9 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 19 Aug 2026 03:14:08 -0400 Subject: [PATCH 2/4] ci: gate releases on production audits --- scripts/audit-production-input.ts | 205 ++++++++++++++++++++++++ scripts/audit-production-policy.ts | 242 ++--------------------------- 2 files changed, 222 insertions(+), 225 deletions(-) create mode 100644 scripts/audit-production-input.ts diff --git a/scripts/audit-production-input.ts b/scripts/audit-production-input.ts new file mode 100644 index 000000000..1e269d6f4 --- /dev/null +++ b/scripts/audit-production-input.ts @@ -0,0 +1,205 @@ +export type AcceptedAdvisory = { + readonly id: string; + readonly package: string; + readonly severity: "high" | "critical"; + readonly affectedArtifacts: readonly string[]; + readonly dependencyPath: string; + readonly reachability: string; + readonly owner: string; + readonly rationale: string; + readonly expires: string; + readonly upstream: readonly string[]; +}; + +export type AuditPolicy = { + readonly auditLevel: "high"; + readonly scope: string; + readonly acceptedAdvisories: readonly AcceptedAdvisory[]; +}; + +export type ProductionManifest = { + readonly name: string; + readonly version: string; + readonly private: true; + readonly [key: string]: unknown; +}; + +export class AuditInputError extends Error {} + +export function isRecord(value: unknown): value is Readonly> { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +export function requiredString(value: unknown, field: string): string { + if ( + typeof value !== "string" || + value.trim() !== value || + value.length === 0 + ) { + throw new AuditInputError(`${field} must be a non-empty trimmed string`); + } + return value; +} + +function stringArray(value: unknown, field: string): readonly string[] { + if (!Array.isArray(value) || value.length === 0) { + throw new AuditInputError(`${field} must be a non-empty string array`); + } + return value.map((item, index) => requiredString(item, `${field}[${index}]`)); +} + +export function isPackageName(value: string): boolean { + return /^(?:@[a-z0-9._~-]+\/)?[a-z0-9._~-]+$/i.test(value); +} + +function parseResolvedNode(value: string, field: string): string { + const separator = value.lastIndexOf("@"); + const packageName = value.slice(0, separator); + const version = value.slice(separator + 1); + if ( + separator < 1 || + !isPackageName(packageName) || + !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version) + ) { + throw new AuditInputError(`${field} must contain exact package@version nodes`); + } + return packageName; +} + +function isoDate(value: unknown, field: string): string { + const date = requiredString(value, field); + const parsed = new Date(`${date}T00:00:00.000Z`); + if ( + !/^\d{4}-\d{2}-\d{2}$/.test(date) || + Number.isNaN(parsed.valueOf()) || + parsed.toISOString().slice(0, 10) !== date + ) { + throw new AuditInputError(`${field} must be a real YYYY-MM-DD date`); + } + return date; +} + +export function httpsUrl(value: string, field: string): string { + let parsed: URL; + try { + parsed = new URL(value); + } catch (error) { + if (error instanceof TypeError) + throw new AuditInputError(`${field} must be a valid HTTPS URL`); + throw error; + } + if ( + parsed.protocol !== "https:" || + parsed.username !== "" || + parsed.password !== "" || + parsed.hostname === "" + ) { + throw new AuditInputError(`${field} must be a credential-free HTTPS URL`); + } + return value; +} + +function parseAcceptedAdvisory( + value: unknown, + index: number, + today: string, +): AcceptedAdvisory { + if (!isRecord(value)) + throw new AuditInputError(`acceptedAdvisories[${index}] must be an object`); + const field = (name: string): string => `acceptedAdvisories[${index}].${name}`; + const id = requiredString(value.id, field("id")); + if ( + !/^(?:GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}|CVE-\d{4}-\d{4,})$/i.test(id) + ) { + throw new AuditInputError(`${field("id")} must be a GHSA or CVE ID`); + } + const packageName = requiredString(value.package, field("package")); + if (!isPackageName(packageName)) + throw new AuditInputError(`${field("package")} is invalid`); + if (value.severity !== "high" && value.severity !== "critical") { + throw new AuditInputError(`${field("severity")} must be high or critical`); + } + const affectedArtifacts = stringArray(value.affectedArtifacts, field("affectedArtifacts")); + if (affectedArtifacts.some((artifact) => !isPackageName(artifact))) { + throw new AuditInputError( + `${field("affectedArtifacts")} contains an invalid package name`, + ); + } + const dependencyPath = requiredString(value.dependencyPath, field("dependencyPath")); + const pathPackages = dependencyPath + .split(" > ") + .map((node, nodeIndex) => + parseResolvedNode(node, `${field("dependencyPath")}[${nodeIndex}]`), + ); + if (pathPackages.at(-1) !== packageName) { + throw new AuditInputError( + `${field("dependencyPath")} must end with ${packageName}@version`, + ); + } + const expires = isoDate(value.expires, field("expires")); + if (expires <= today) throw new AuditInputError(`${id} acceptance expired on ${expires}`); + const upstream = stringArray(value.upstream, field("upstream")).map((url, urlIndex) => + httpsUrl(url, `${field("upstream")}[${urlIndex}]`), + ); + return { + id, + package: packageName, + severity: value.severity, + affectedArtifacts, + dependencyPath, + reachability: requiredString(value.reachability, field("reachability")), + owner: requiredString(value.owner, field("owner")), + rationale: requiredString(value.rationale, field("rationale")), + expires, + upstream, + }; +} + +export function parsePolicy(value: unknown, today: string): AuditPolicy { + if (!isRecord(value)) + throw new AuditInputError("production audit policy must be an object"); + if (value.auditLevel !== "high") + throw new AuditInputError("production audit policy auditLevel must be high"); + if (!Array.isArray(value.acceptedAdvisories)) { + throw new AuditInputError( + "production audit policy acceptedAdvisories must be an array", + ); + } + isoDate(today, "current date"); + return { + auditLevel: "high", + scope: requiredString(value.scope, "production audit policy scope"), + acceptedAdvisories: value.acceptedAdvisories.map((entry, index) => + parseAcceptedAdvisory(entry, index, today), + ), + }; +} + +function dependencyGroup( + value: unknown, + field: string, +): Readonly> | undefined { + if (value === undefined) return undefined; + if (!isRecord(value)) throw new AuditInputError(`${field} must be an object`); + return value; +} + +export function createProductionManifest(value: unknown): ProductionManifest { + if (!isRecord(value)) + throw new AuditInputError("package manifest must be an object"); + const manifest: Record & ProductionManifest = { + name: requiredString(value.name, "package name"), + version: requiredString(value.version, "package version"), + private: true, + }; + for (const group of [ + "dependencies", + "optionalDependencies", + "peerDependencies", + "peerDependenciesMeta", + ] as const) { + const dependencies = dependencyGroup(value[group], group); + if (dependencies !== undefined) manifest[group] = dependencies; + } + return manifest; +} diff --git a/scripts/audit-production-policy.ts b/scripts/audit-production-policy.ts index 92ba60e69..200b9a179 100644 --- a/scripts/audit-production-policy.ts +++ b/scripts/audit-production-policy.ts @@ -1,28 +1,17 @@ -type AcceptedAdvisory = { - readonly id: string; - readonly package: string; - readonly severity: "high" | "critical"; - readonly affectedArtifacts: readonly string[]; - readonly dependencyPath: string; - readonly reachability: string; - readonly owner: string; - readonly rationale: string; - readonly expires: string; - readonly upstream: readonly string[]; -}; - -export type AuditPolicy = { - readonly auditLevel: "high"; - readonly scope: string; - readonly acceptedAdvisories: readonly AcceptedAdvisory[]; -}; - -export type ProductionManifest = { - readonly name: string; - readonly version: string; - readonly private: true; - readonly [key: string]: unknown; -}; +import type { AcceptedAdvisory, AuditPolicy } from "./audit-production-input"; +import { + AuditInputError, + httpsUrl, + isPackageName, + isRecord, + requiredString, +} from "./audit-production-input"; + +export { + AuditInputError, + createProductionManifest, + parsePolicy, +} from "./audit-production-input"; type Finding = { readonly id: string; @@ -39,198 +28,6 @@ type Evaluation = { readonly rejected: readonly Finding[]; }; -export class AuditInputError extends Error {} - -function isRecord(value: unknown): value is Readonly> { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - -function requiredString(value: unknown, field: string): string { - if ( - typeof value !== "string" || - value.trim() !== value || - value.length === 0 - ) { - throw new AuditInputError(`${field} must be a non-empty trimmed string`); - } - return value; -} - -function stringArray(value: unknown, field: string): readonly string[] { - if (!Array.isArray(value) || value.length === 0) { - throw new AuditInputError(`${field} must be a non-empty string array`); - } - return value.map((item, index) => requiredString(item, `${field}[${index}]`)); -} - -function isPackageName(value: string): boolean { - return /^(?:@[a-z0-9._~-]+\/)?[a-z0-9._~-]+$/i.test(value); -} - -function parseResolvedNode(value: string, field: string): string { - const separator = value.lastIndexOf("@"); - const packageName = value.slice(0, separator); - const version = value.slice(separator + 1); - if ( - separator < 1 || - !isPackageName(packageName) || - !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version) - ) { - throw new AuditInputError( - `${field} must contain exact package@version nodes`, - ); - } - return packageName; -} - -function isoDate(value: unknown, field: string): string { - const date = requiredString(value, field); - const parsed = new Date(`${date}T00:00:00.000Z`); - if ( - !/^\d{4}-\d{2}-\d{2}$/.test(date) || - Number.isNaN(parsed.valueOf()) || - parsed.toISOString().slice(0, 10) !== date - ) { - throw new AuditInputError(`${field} must be a real YYYY-MM-DD date`); - } - return date; -} - -function httpsUrl(value: string, field: string): string { - let parsed: URL; - try { - parsed = new URL(value); - } catch (error) { - if (error instanceof TypeError) - throw new AuditInputError(`${field} must be a valid HTTPS URL`); - throw error; - } - if ( - parsed.protocol !== "https:" || - parsed.username !== "" || - parsed.password !== "" || - parsed.hostname === "" - ) { - throw new AuditInputError(`${field} must be a credential-free HTTPS URL`); - } - return value; -} - -function parseAcceptedAdvisory( - value: unknown, - index: number, - today: string, -): AcceptedAdvisory { - if (!isRecord(value)) - throw new AuditInputError(`acceptedAdvisories[${index}] must be an object`); - const field = (name: string): string => - `acceptedAdvisories[${index}].${name}`; - const id = requiredString(value.id, field("id")); - if ( - !/^(?:GHSA-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}|CVE-\d{4}-\d{4,})$/i.test(id) - ) { - throw new AuditInputError(`${field("id")} must be a GHSA or CVE ID`); - } - const packageName = requiredString(value.package, field("package")); - if (!isPackageName(packageName)) - throw new AuditInputError(`${field("package")} is invalid`); - if (value.severity !== "high" && value.severity !== "critical") { - throw new AuditInputError(`${field("severity")} must be high or critical`); - } - const affectedArtifacts = stringArray( - value.affectedArtifacts, - field("affectedArtifacts"), - ); - if (affectedArtifacts.some((artifact) => !isPackageName(artifact))) { - throw new AuditInputError( - `${field("affectedArtifacts")} contains an invalid package name`, - ); - } - const dependencyPath = requiredString( - value.dependencyPath, - field("dependencyPath"), - ); - const pathPackages = dependencyPath - .split(" > ") - .map((node, nodeIndex) => - parseResolvedNode(node, `${field("dependencyPath")}[${nodeIndex}]`), - ); - if (pathPackages.at(-1) !== packageName) { - throw new AuditInputError( - `${field("dependencyPath")} must end with ${packageName}@version`, - ); - } - const expires = isoDate(value.expires, field("expires")); - if (expires <= today) - throw new AuditInputError(`${id} acceptance expired on ${expires}`); - const upstream = stringArray(value.upstream, field("upstream")).map( - (url, urlIndex) => httpsUrl(url, `${field("upstream")}[${urlIndex}]`), - ); - return { - id, - package: packageName, - severity: value.severity, - affectedArtifacts, - dependencyPath, - reachability: requiredString(value.reachability, field("reachability")), - owner: requiredString(value.owner, field("owner")), - rationale: requiredString(value.rationale, field("rationale")), - expires, - upstream, - }; -} - -export function parsePolicy(value: unknown, today: string): AuditPolicy { - if (!isRecord(value)) - throw new AuditInputError("production audit policy must be an object"); - if (value.auditLevel !== "high") - throw new AuditInputError( - "production audit policy auditLevel must be high", - ); - if (!Array.isArray(value.acceptedAdvisories)) { - throw new AuditInputError( - "production audit policy acceptedAdvisories must be an array", - ); - } - isoDate(today, "current date"); - return { - auditLevel: "high", - scope: requiredString(value.scope, "production audit policy scope"), - acceptedAdvisories: value.acceptedAdvisories.map((entry, index) => - parseAcceptedAdvisory(entry, index, today), - ), - }; -} - -function dependencyGroup( - value: unknown, - field: string, -): Readonly> | undefined { - if (value === undefined) return undefined; - if (!isRecord(value)) throw new AuditInputError(`${field} must be an object`); - return value; -} - -export function createProductionManifest(value: unknown): ProductionManifest { - if (!isRecord(value)) - throw new AuditInputError("package manifest must be an object"); - const manifest: Record & ProductionManifest = { - name: requiredString(value.name, "package name"), - version: requiredString(value.version, "package version"), - private: true, - }; - for (const group of [ - "dependencies", - "optionalDependencies", - "peerDependencies", - "peerDependenciesMeta", - ] as const) { - const dependencies = dependencyGroup(value[group], group); - if (dependencies !== undefined) manifest[group] = dependencies; - } - return manifest; -} - export function resolvedDependencyPaths( output: string, artifact: string, @@ -264,9 +61,7 @@ function auditEntries(value: unknown): readonly [string, readonly unknown[]][] { throw new AuditInputError("bun audit JSON must be an object"); return Object.entries(value).map(([packageName, advisories]) => { if (!isPackageName(packageName) || !Array.isArray(advisories)) { - throw new AuditInputError( - "bun audit JSON contains an invalid package entry", - ); + throw new AuditInputError("bun audit JSON contains an invalid package entry"); } return [packageName, advisories] as const; }); @@ -282,11 +77,8 @@ function parseFinding( dependencyPath: string, ): Finding | undefined { if (!isRecord(value)) - throw new AuditInputError( - `bun audit advisory for ${packageName} must be an object`, - ); - if (value.severity !== "high" && value.severity !== "critical") - return undefined; + throw new AuditInputError(`bun audit advisory for ${packageName} must be an object`); + if (value.severity !== "high" && value.severity !== "critical") return undefined; const url = httpsUrl( requiredString(value.url, `${packageName} advisory URL`), `${packageName} advisory URL`, From 732fc3dc643cdf3ffc2c26a4e2e835708d0302a0 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 19 Aug 2026 04:43:35 -0400 Subject: [PATCH 3/4] test(tui): stabilize runtime import order --- packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts b/packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts index e763d2ac5..2a81f672b 100644 --- a/packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts +++ b/packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts @@ -49,6 +49,9 @@ describe("compiled TUI runtime imports", () => { * Built from the shared list so the test cannot cover fewer modules than the * build rewrites. */ async function loadExportSets(): Promise>> { + // @opentui/core/testing subclasses a core export during module initialization; + // warm core before the parallel imports so Bun cannot expose its TDZ. + await import("@opentui/core"); const entries = await Promise.all( TUI_RUNTIME_SPECIFIERS.map( async (specifier) => From 6223f7d6499341f683bc3165a89a5046315c7a4e Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 19 Aug 2026 06:51:28 -0400 Subject: [PATCH 4/4] ci: scope production audit Bun pin --- .github/workflows/ci.yml | 19 +++++++++---------- .github/workflows/release.yml | 3 +-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1eb081878..3b957a113 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,6 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - BUN_VERSION: "1.3.10" # Pipeline shape on every push to master and on every PR: # @@ -37,7 +36,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: "1.3.10" - name: Test production audit policy run: bun run test:audit-production @@ -53,7 +52,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: latest # node:sqlite (the Pi / OpenCode-Desktop SQLite backend) is only reachable # under Node, not Bun. Node 24 also strips the smoke script's inline TS. @@ -121,7 +120,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile @@ -146,7 +145,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile @@ -177,7 +176,7 @@ jobs: - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: latest - name: Install workspace deps run: bun install --frozen-lockfile @@ -210,7 +209,7 @@ jobs: - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: latest - name: Install workspace deps run: bun install --frozen-lockfile @@ -247,7 +246,7 @@ jobs: - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: latest - name: Install workspace deps run: bun install --frozen-lockfile @@ -283,7 +282,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: latest - name: Install workspace deps run: bun install --frozen-lockfile @@ -345,7 +344,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: latest # Pi tests resolve the Pi binary via createRequire against # @earendil-works/pi-coding-agent, which is a workspace dep of diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 331cf58fd..43ad2b639 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,6 @@ permissions: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - BUN_VERSION: "1.3.10" # Pipeline shape (each stage gates the next via `needs:`): # @@ -56,7 +55,7 @@ jobs: - uses: oven-sh/setup-bun@v2 with: - bun-version: ${{ env.BUN_VERSION }} + bun-version: "1.3.10" - name: Test production audit policy run: bun run test:audit-production