Files
gstack/lib/cso/contracts.ts
T
garrytan d93d61f7ba style(cso): format lib/cso TypeScript with pinned Prettier
Mechanical reformat only. Minified transpile output is byte-identical for
21 of 22 files; witness.ts differs only in three regex flag orders
(/mi -> /im), which JavaScript canonicalizes. Source-text assertions over
lib/cso now compare whitespace-insensitively with the same tokens.
2026-09-29 14:22:48 +00:00

1002 lines
38 KiB
TypeScript

/** CSO's versioned, host-independent evidence contract. Runtime claims are helper-owned. */
import { createHash } from 'node:crypto';
export const ABI = 3;
export const MAX_OUTPUT = 1024 * 1024;
const UNSAFE_STRING_CONTROLS =
/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/u;
const UNSAFE_PROPERTY_CONTROLS = /[\x00-\x1f\x7f-\x9f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/u;
export type Completeness = 'complete' | 'partial' | 'not assessed';
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'informational';
export type ErrorCode =
| 'INVALID_ARGUMENT'
| 'INVALID_SCHEMA'
| 'MISSING_INPUT'
| 'SNAPSHOT_RACE'
| 'UNSAFE_PATH'
| 'REDACTION_FAILED'
| 'PERSISTENCE_FAILED'
| 'TOOL_UNAVAILABLE'
| 'TOOL_FAILED'
| 'ISOLATION_FAILED'
| 'INSUFFICIENT_CAPACITY'
| 'DEADLINE'
| 'CANCELLED'
| 'PREREQUISITE'
| 'INCOMPATIBLE_INPUT'
| 'ASSERTION_FAILED';
export class CsoError extends Error {
constructor(
public code: ErrorCode,
message: string,
) {
super(message);
this.name = 'CsoError';
}
}
export const sha256 = (value: string | Buffer): string => createHash('sha256').update(value).digest('hex');
export const canonical = (value: unknown): string => {
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
if (value && typeof value === 'object')
return `{${Object.keys(value)
.sort()
.map((k) => `${JSON.stringify(k)}:${canonical((value as any)[k])}`)
.join(',')}}`;
return JSON.stringify(value);
};
export interface CoverageRecord {
domain: string;
scope: string;
status: 'assessed' | 'partial' | 'not_assessed' | 'not_applicable';
method: string;
gaps: string[];
exclusions: string[];
evidence: string[];
tool?: { name: string; version: string; freshness: string; outcome: string };
}
export interface ApplicationModel {
actors: string[];
assets: string[];
entrypoints: string[];
tenantBoundaries: string[];
sensitiveOperations: string[];
invariants: string[];
}
export interface FindingV3 {
id: string;
fingerprint: string;
title: string;
rootCause: string;
location: { path: string; line: number; symbol: string };
advisoryIds: string[];
severity: Severity;
confidence: 'high' | 'medium' | 'low';
confidenceRationale: string;
evidence: 'supported' | 'hypothesis' | 'legacy_review';
attackerControl: string;
impact: string;
scenario: string;
trace: string[];
references: string[];
recommendation: string;
challenge: {
reviewer: string;
independent: boolean;
mode: 'independent_agent' | 'sequential_fallback';
callers: string;
controls: string;
counterevidence: string;
conclusion: string;
};
dependency?: {
affectedVersion: string;
reachability: 'reachable' | 'unreachable' | 'unknown';
exposure: string;
exploitation: string;
};
reproduction: 'not_attempted' | 'blocked' | 'inconclusive' | 'disproved' | 'reproduced';
repair: 'not_attempted' | 'proposed' | 'failed' | 'runtime_tested' | 'tested';
closure: 'open' | 'resolved' | 'unknown';
verificationId?: string;
reproductionAttemptId?: string;
verificationAssurance?: {
assertions: 'authenticated_out_of_process';
testCompletion: 'self_reported' | 'authenticated_out_of_process';
review: 'self_attested' | 'host_verified';
};
}
export interface RunPolicy {
mode: 'daily' | 'comprehensive';
scope: string;
diff: boolean;
base: string;
offline: boolean;
budgetSeconds: number;
maxWorkers: 3;
maxRepairs: 3;
}
export interface RunReportV3 {
schemaVersion: 3;
runId: string;
repoId: string;
createdAt: string;
deadline: string;
status: 'running' | 'finished' | 'interrupted';
completeness: Completeness;
policy: RunPolicy;
source: {
root: string;
snapshotHash: string;
originalHash: string;
baseCommit?: string;
transformations?: Array<{ path: string; handling: string }>;
};
application: ApplicationModel;
coverage: CoverageRecord[];
findings: FindingV3[];
gaps: string[];
events: { at: string; kind: string; message: string }[];
parent?: { runId: string; findingId: string; kind: 'recheck' };
modelUsage?: { source: string; tokens: number; cost?: number };
}
export interface SnapshotEntry {
path: string;
pathId: string;
originalHash: string;
executionHash?: string;
bytes: number;
mode: number;
transformation?: string;
}
export interface SnapshotPathIdentity {
path: string;
pathId: string;
}
export interface SnapshotManifest {
version: 3;
createdAt: string;
expiresAt: string;
root: string;
originalHash: string;
executionHash: string;
entries: SnapshotEntry[];
deletedPaths?: SnapshotPathIdentity[];
headCommit?: string;
baseCommit?: string;
changedPaths?: string[];
}
export interface HttpAssertion {
name: string;
path: string;
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
headers?: Record<string, string>;
body?: string;
expected: { status: number; includes?: string; excludes?: string };
vulnerable?: { status: number; includes?: string; excludes?: string };
}
export interface Command {
executable: string;
args: string[];
}
export interface VerificationRequest {
findingId: string;
runtimeProfile: string;
port: number;
start: Command;
legitimate: HttpAssertion[];
security: HttpAssertion;
existingTests: Command[];
fixtures: Record<string, string>;
boundaryFiles: string[];
testFiles: string[];
changes: {
path: string;
beforeSha256: string | null;
after: string | null;
effect: 'source' | 'configuration' | 'dependency';
}[];
review: {
reviewer: string;
independent: boolean;
rootCauseRepaired: boolean;
featurePreserved: boolean;
boundaryMocks: boolean;
rationale: string;
reviewedPatchHash: string;
artifactId?: string;
};
}
export interface RepairReviewArtifact {
schemaVersion: 3;
id: string;
runId: string;
findingId: string;
createdAt: string;
producer: string;
reviewer: string;
assurance: 'self_attested' | 'host_verified';
requestHash: string;
patchHash: string;
rootCauseRepaired: boolean;
featurePreserved: boolean;
boundaryMocks: boolean;
rationale: string;
}
export interface RecheckEvidenceV3 {
kind: 'caller' | 'security_boundary';
path: string;
line: number;
observation: string;
}
export interface SubmissionV3 {
application?: ApplicationModel;
findings?: unknown[];
coverage?: unknown[];
gaps?: string[];
modelUsage?: { source: string; tokens: number; cost?: number };
recheck?: {
findingId: string;
outcome: 'open' | 'resolved' | 'unknown';
evidence: RecheckEvidenceV3[];
rootCause: string;
};
}
export interface VerificationObservation {
booted: boolean;
legitimate: boolean;
security: 'pass' | 'intended_failure' | 'inconclusive';
existingTests: boolean;
output: string;
inputHash: string;
}
export interface AssertionWitnessBinding {
schemaVersion: 1;
protocol: 'gstack-cso-assertion-witness-v1';
nonce: string;
phase: 'before' | 'after';
issuedAt: string;
expiresAt: string;
runId: string;
findingId: string;
policyHash: string;
auditPolicyHash: string;
runtime: { image: string; verifierImage: string; platform: string; profile: string };
runner: {
testToolchain: 'runtime' | 'project';
startPlanHash: string;
testPlanHash: string;
commandsHash: string;
minimumPassingTestsHash: string;
};
sourceHash: string;
dependencyHash: string;
configurationHash: string;
requestHash: string;
patchHash: string;
harnessHash: string;
assertionHash: string;
fixturesHash: string;
}
export interface AssertionWitnessReceipt {
schemaVersion: 1;
binding: AssertionWitnessBinding;
keyId: string;
publicKey: string;
observationHash: string;
externalAssertionsPassed: boolean;
diagnosticTestsPassed: boolean;
executions: Array<{
commandHash: string;
exitCode: number;
outputHash: string;
minimumPassingTests: number;
executedTests: number;
passingTests: number;
reportedPassed: boolean;
}>;
signature: string;
}
export interface PreparationProof {
schemaVersion: 1;
dependencyClosureHash: string;
configurationHash: string;
sourceProjectionHash: string;
preparedManifestHash: string;
preparedDependencyHash: string;
receiptHash: string;
executionEnvironmentHash: string;
databaseHash: string;
transformations: Array<{ path: string; sha256: string; mode: number; reason: string }>;
}
export interface VerificationManifest {
version: 3;
id: string;
runId: string;
findingId: string;
createdAt: string;
helperAbi: 3;
runtime: { image: string; platform: string; profile: string };
testToolchain: 'runtime' | 'project';
policyHash: string;
harnessHash: string;
requestHash: string;
startPlanHash: string;
testPlanHash: string;
fixturesHash: string;
patchHash: string;
auditPolicyHash: string;
originalSourceHash: string;
transformationsHash: string;
archivesHash: string;
preparationHash?: string;
beforeSourceHash: string;
afterSourceHash: string;
beforeDependencies: string;
afterDependencies: string;
beforeConfiguration: string;
afterConfiguration: string;
before: VerificationObservation;
after: VerificationObservation;
review: VerificationRequest['review'];
reviewAssurance: 'self_attested' | 'host_verified';
assertionAssurance?: 'authenticated_out_of_process';
testCompletionAssurance: 'self_reported' | 'authenticated_out_of_process';
witnessHash?: string;
result: 'runtime_tested' | 'tested' | 'failed' | 'inconclusive';
}
export interface RepairBundle {
schemaVersion: 3;
runId: string;
id: string;
createdAt: string;
expiresAt: string;
requiredInputs: {
sourceHash: string;
originalHash: string;
runtimeImage: string;
platform: string;
archives: string[];
dependencyClosures?: { before: unknown; after: unknown };
};
request: VerificationRequest;
verification: VerificationManifest;
transformations: SnapshotEntry[];
preparation?: { before: PreparationProof; after: PreparationProof };
reviewArtifact?: RepairReviewArtifact;
witness?: { before: AssertionWitnessReceipt; after: AssertionWitnessReceipt };
}
export function object(value: unknown, name = 'input'): Record<string, any> {
if (!value || typeof value !== 'object' || Array.isArray(value))
throw new CsoError('INVALID_SCHEMA', `${name} must be an object`);
return value as Record<string, any>;
}
function exact(value: Record<string, any>, allowed: readonly string[], name: string): void {
for (const key of Object.keys(value))
if (!allowed.includes(key)) throw new CsoError('INVALID_SCHEMA', `Unexpected ${name} field: ${key}`);
}
function boolean(value: unknown, name: string): boolean {
if (typeof value !== 'boolean') throw new CsoError('INVALID_SCHEMA', `${name} must be a boolean`);
return value;
}
export function string(value: unknown, name: string, max = 8192): string {
if (typeof value !== 'string' || !value.trim() || value.length > max || UNSAFE_STRING_CONTROLS.test(value))
throw new CsoError(
'INVALID_SCHEMA',
`${name} must be a nonempty string without unsafe control characters (maximum ${max})`,
);
return value;
}
export function strings(value: unknown, name: string): string[] {
if (!Array.isArray(value) || value.length > 1000)
throw new CsoError('INVALID_SCHEMA', `${name} must be an array`);
return value.map((v, i) => string(v, `${name}[${i}]`));
}
export function oneOf<T extends string>(value: unknown, choices: readonly T[], name: string): T {
if (!choices.includes(value as T))
throw new CsoError('INVALID_SCHEMA', `${name} must be one of ${choices.join(', ')}`);
return value as T;
}
export function relativePath(value: unknown): string {
const p = string(value, 'relative path', 4096);
if (
p.startsWith('/') ||
p.includes('\\') ||
/^[A-Za-z]:/.test(p) ||
p.split('/').some((x) => !x || x === '.' || x === '..') ||
/[\x00-\x1f\x7f]/.test(p)
)
throw new CsoError('UNSAFE_PATH', 'Expected a contained relative path');
return p;
}
const SNAPSHOT_PATH_HANDLE = /^@cso-path\/\/([a-f0-9]{32})$/;
export function snapshotPathId(root: string, path: string): string {
// Validate the root for callers, but do not salt the opaque identity with its
// absolute checkout path. Replay must resolve the same retained path after a
// matching source tree is supplied from another checkout.
string(root, 'snapshot root', 8192);
const relative = relativePath(path);
return sha256(canonical({ kind: 'cso-path-v3', path: relative })).slice(0, 32);
}
export function snapshotPathHandle(pathId: string): string {
if (!/^[a-f0-9]{32}$/.test(pathId))
throw new CsoError('INVALID_SCHEMA', 'Snapshot path ID must be 32 lowercase hexadecimal characters');
return `@cso-path//${pathId}`;
}
export function snapshotPathHandleId(value: unknown): string | undefined {
if (typeof value !== 'string') return;
return SNAPSHOT_PATH_HANDLE.exec(value)?.[1];
}
export function snapshotReference(value: unknown): string {
const reference = string(value, 'snapshot path or handle', 4096);
return snapshotPathHandleId(reference) ? reference : relativePath(reference);
}
export function snapshotOriginalIdentity(
entries: Array<Pick<SnapshotEntry, 'path' | 'originalHash' | 'mode'>>,
deletedPaths: Array<Pick<SnapshotPathIdentity, 'path'>> = [],
): string {
const present = entries.map((entry) => [entry.path, entry.originalHash, entry.mode]);
// Preserve the original no-deletion identity for v3 artifacts already
// retained by pre-release builds. Any deletion changes the identity and is
// therefore impossible to strip from a manifest without detection.
return sha256(
canonical(
deletedPaths.length
? { entries: present, deletedPaths: deletedPaths.map((item) => item.path).sort() }
: present,
),
);
}
export function rootCauseIdentity(value: string): string {
return value.normalize('NFKC').trim().replace(/\s+/g, ' ').toLowerCase();
}
function advisoryIdentities(values: string[]): string[] {
return [...new Set(values.map((value) => value.normalize('NFKC').trim().toUpperCase()))].sort();
}
export function fingerprint(f: Pick<FindingV3, 'rootCause' | 'location' | 'advisoryIds'>): string {
// Titles, line shifts, severity, and generated descriptions are deliberately absent.
return sha256(
canonical({
rootCause: rootCauseIdentity(f.rootCause),
path: f.location.path,
symbol: f.location.symbol,
advisories: advisoryIdentities(f.advisoryIds),
}),
).slice(0, 32);
}
export function validateFinding(input: unknown): FindingV3 {
const v = object(input, 'finding'),
loc = object(v.location, 'location'),
c = object(v.challenge, 'challenge');
for (const reserved of [
'reproduction',
'repair',
'closure',
'verificationId',
'reproductionAttemptId',
'verificationAssurance',
])
if (reserved in v) throw new CsoError('INVALID_SCHEMA', `${reserved} is helper-owned`);
exact(
v,
[
'title',
'rootCause',
'location',
'advisoryIds',
'severity',
'confidence',
'confidenceRationale',
'evidence',
'attackerControl',
'impact',
'scenario',
'trace',
'references',
'recommendation',
'challenge',
'dependency',
],
'finding',
);
exact(loc, ['path', 'line', 'symbol'], 'location');
exact(
c,
['reviewer', 'independent', 'mode', 'callers', 'controls', 'counterevidence', 'conclusion'],
'challenge',
);
const f: FindingV3 = {
id: '',
fingerprint: '',
title: string(v.title, 'title'),
rootCause: string(v.rootCause, 'rootCause'),
location: { path: snapshotReference(loc.path), line: loc.line, symbol: string(loc.symbol, 'symbol') },
advisoryIds: advisoryIdentities(strings(v.advisoryIds ?? [], 'advisoryIds')),
severity: oneOf(v.severity, ['critical', 'high', 'medium', 'low', 'informational'], 'severity'),
confidence: oneOf(v.confidence, ['high', 'medium', 'low'], 'confidence'),
confidenceRationale: string(v.confidenceRationale, 'confidenceRationale'),
evidence: oneOf(v.evidence, ['supported', 'hypothesis'], 'evidence'),
attackerControl: string(v.attackerControl, 'attackerControl'),
impact: string(v.impact, 'impact'),
scenario: string(v.scenario, 'scenario'),
trace: strings(v.trace, 'trace'),
references: strings(v.references, 'references'),
recommendation: string(v.recommendation, 'recommendation'),
challenge: {
reviewer: string(c.reviewer, 'reviewer'),
independent: boolean(c.independent, 'challenge.independent'),
mode: oneOf(c.mode, ['independent_agent', 'sequential_fallback'], 'challenge.mode'),
callers: string(c.callers, 'callers'),
controls: string(c.controls, 'controls'),
counterevidence: string(c.counterevidence, 'counterevidence'),
conclusion: string(c.conclusion, 'conclusion'),
},
reproduction: 'not_attempted',
repair: 'not_attempted',
closure: 'open',
};
if (!Number.isInteger(f.location.line) || f.location.line < 1)
throw new CsoError('INVALID_SCHEMA', 'line must be a positive integer');
const fallbackLabel = 'sequential challenge; independent agent unavailable';
if (
(f.challenge.independent &&
(f.challenge.mode !== 'independent_agent' || f.challenge.reviewer === fallbackLabel)) ||
(!f.challenge.independent &&
(f.challenge.mode !== 'sequential_fallback' || f.challenge.reviewer !== fallbackLabel))
)
throw new CsoError(
'INVALID_SCHEMA',
`Challenge mode must bind either an independent agent or the exact fallback label: ${fallbackLabel}`,
);
if (f.evidence === 'supported' && (f.confidence === 'low' || !f.trace.length || !f.references.length))
throw new CsoError(
'INVALID_SCHEMA',
'Supported findings require a challenge, a trace, supporting references, and medium/high confidence',
);
if (v.dependency) {
const d = object(v.dependency);
exact(d, ['affectedVersion', 'reachability', 'exposure', 'exploitation'], 'dependency');
f.dependency = {
affectedVersion: string(d.affectedVersion, 'affectedVersion'),
reachability: oneOf(d.reachability, ['reachable', 'unreachable', 'unknown'], 'reachability'),
exposure: string(d.exposure, 'exposure'),
exploitation: string(d.exploitation, 'exploitation'),
};
}
f.fingerprint = fingerprint(f);
f.id = f.fingerprint;
return f;
}
export function validateCoverage(input: unknown): CoverageRecord {
const v = object(input, 'coverage');
exact(v, ['domain', 'scope', 'status', 'method', 'gaps', 'exclusions', 'evidence', 'tool'], 'coverage');
const c: CoverageRecord = {
domain: string(v.domain, 'domain'),
scope: string(v.scope, 'scope'),
status: oneOf(v.status, ['assessed', 'partial', 'not_assessed', 'not_applicable'], 'coverage status'),
method: string(v.method, 'method'),
gaps: strings(v.gaps, 'gaps'),
exclusions: strings(v.exclusions, 'exclusions'),
evidence: strings(v.evidence, 'evidence'),
};
if (c.status === 'assessed' && (c.gaps.length || !c.evidence.length))
throw new CsoError('INVALID_SCHEMA', 'Assessed coverage needs evidence and no outstanding gaps');
if (c.status === 'partial' && (!c.gaps.length || !c.evidence.length))
throw new CsoError('INVALID_SCHEMA', 'Partial coverage needs assessed evidence and a concrete gap');
if (c.status === 'not_assessed' && !c.gaps.length)
throw new CsoError('INVALID_SCHEMA', 'Unassessed coverage needs a concrete gap');
if (c.status === 'not_applicable' && (!c.evidence.length || c.gaps.length))
throw new CsoError(
'INVALID_SCHEMA',
'Non-applicability requires evidence and cannot retain an assessment gap',
);
if (v.tool) {
const t = object(v.tool);
exact(t, ['name', 'version', 'freshness', 'outcome'], 'coverage tool');
c.tool = {
name: string(t.name, 'tool name'),
version: string(t.version, 'tool version'),
freshness: string(t.freshness, 'freshness'),
outcome: string(t.outcome, 'outcome'),
};
}
return c;
}
export function validateCommand(value: unknown, name: string): Command {
const v = object(value, name),
executable = string(v.executable, `${name}.executable`, 4096),
args = strings(v.args ?? [], `${name}.args`);
exact(v, ['executable', 'args'], name);
if (!executable.startsWith('/') || executable.includes('..'))
throw new CsoError('INVALID_SCHEMA', `${name}.executable must be an absolute in-container path`);
return { executable, args };
}
export function validateVerificationObservation(value: unknown): VerificationObservation {
const v = object(value, 'verification observation');
for (const key of Object.keys(v))
if (!['booted', 'legitimate', 'security', 'existingTests', 'output', 'inputHash'].includes(key))
throw new CsoError('INVALID_SCHEMA', `Unexpected verification observation field: ${key}`);
if (
typeof v.booted !== 'boolean' ||
typeof v.legitimate !== 'boolean' ||
typeof v.existingTests !== 'boolean'
)
throw new CsoError('INVALID_SCHEMA', 'Verification observation outcomes must be booleans');
if (typeof v.output !== 'string' || v.output.length > 8192 || v.output.includes('\0'))
throw new CsoError('INVALID_SCHEMA', 'Verification observation output must be a bounded string');
if (typeof v.inputHash !== 'string' || (!/^$/.test(v.inputHash) && !/^[a-f0-9]{64}$/.test(v.inputHash)))
throw new CsoError('INVALID_SCHEMA', 'Verification observation inputHash must be empty or a sha256 hash');
return {
booted: v.booted,
legitimate: v.legitimate,
security: oneOf(
v.security,
['pass', 'intended_failure', 'inconclusive'],
'verification security outcome',
),
existingTests: v.existingTests,
output: v.output,
inputHash: v.inputHash,
};
}
function assertion(value: unknown, name: string): HttpAssertion {
const v = object(value, name),
expected = object(v.expected, `${name}.expected`);
exact(v, ['name', 'path', 'method', 'headers', 'body', 'expected', 'vulnerable'], name);
const oracle = (x: Record<string, any>, n: string) => {
exact(x, ['status', 'includes', 'excludes'], n);
if (!Number.isInteger(x.status) || x.status < 100 || x.status > 599)
throw new CsoError('INVALID_SCHEMA', `${n}.status must be an HTTP status`);
return {
status: x.status,
...(x.includes === undefined ? {} : { includes: string(x.includes, `${n}.includes`) }),
...(x.excludes === undefined ? {} : { excludes: string(x.excludes, `${n}.excludes`) }),
};
};
const path = string(v.path, `${name}.path`, 4096);
if (!path.startsWith('/') || path.startsWith('//') || /[\r\n]/.test(path))
throw new CsoError('INVALID_SCHEMA', `${name}.path must stay on numeric loopback`);
const headers: Record<string, string> = {};
if (v.headers !== undefined)
for (const [k, val] of Object.entries(object(v.headers, `${name}.headers`))) {
if (
!/^[A-Za-z0-9-]{1,100}$/.test(k) ||
typeof val !== 'string' ||
val.length > 8192 ||
/[\r\n]/.test(val)
)
throw new CsoError('INVALID_SCHEMA', `Invalid ${name} header`);
headers[k] = val;
}
return {
name: string(v.name, `${name}.name`),
path,
method: oneOf(v.method, ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], `${name}.method`),
...(Object.keys(headers).length ? { headers } : {}),
...(v.body === undefined ? {} : { body: string(v.body, `${name}.body`, 65536) }),
expected: oracle(expected, `${name}.expected`),
...(v.vulnerable === undefined ? {} : { vulnerable: oracle(object(v.vulnerable), `${name}.vulnerable`) }),
};
}
export function validateVerificationRequest(input: unknown): VerificationRequest {
const v = object(input, 'verification request'),
changes = v.changes,
fixtures = object(v.fixtures ?? {}, 'fixtures'),
review = object(v.review, 'review');
exact(
v,
[
'findingId',
'runtimeProfile',
'port',
'start',
'legitimate',
'security',
'existingTests',
'fixtures',
'boundaryFiles',
'testFiles',
'changes',
'review',
],
'verification request',
);
exact(
review,
[
'reviewer',
'independent',
'rootCauseRepaired',
'featurePreserved',
'boundaryMocks',
'rationale',
'reviewedPatchHash',
'artifactId',
],
'review',
);
if (!Array.isArray(changes) || !changes.length || changes.length > 100)
throw new CsoError('INVALID_SCHEMA', 'changes must contain 1..100 declared patch effects');
const cleanFixtures: Record<string, string> = {};
for (const [p, body] of Object.entries(fixtures)) {
cleanFixtures[relativePath(p)] = string(body, `fixture ${p}`, 1024 * 1024);
}
const request: VerificationRequest = {
findingId: string(v.findingId, 'findingId'),
runtimeProfile: string(v.runtimeProfile, 'runtimeProfile', 100),
port: v.port,
start: validateCommand(v.start, 'start'),
legitimate: (Array.isArray(v.legitimate) ? v.legitimate : []).map((x, i) =>
assertion(x, `legitimate[${i}]`),
),
security: assertion(v.security, 'security'),
existingTests: (Array.isArray(v.existingTests) ? v.existingTests : []).map((x, i) =>
validateCommand(x, `existingTests[${i}]`),
),
fixtures: cleanFixtures,
boundaryFiles: strings(v.boundaryFiles, 'boundaryFiles').map(snapshotReference),
testFiles: strings(v.testFiles, 'testFiles').map(snapshotReference),
changes: changes.map((raw: any, i: number) => {
const x = object(raw, `changes[${i}]`),
before = x.beforeSha256;
exact(x, ['path', 'beforeSha256', 'after', 'effect'], `changes[${i}]`);
if (before !== null && (typeof before !== 'string' || !/^[a-f0-9]{64}$/.test(before)))
throw new CsoError('INVALID_SCHEMA', `changes[${i}].beforeSha256 must be a hash or null`);
return {
path: snapshotReference(x.path),
beforeSha256: before,
after: x.after === null ? null : string(x.after, `changes[${i}].after`, 1024 * 1024),
effect: oneOf(x.effect, ['source', 'configuration', 'dependency'], `changes[${i}].effect`),
};
}),
review: {
reviewer: string(review.reviewer, 'reviewer'),
independent: boolean(review.independent, 'review.independent'),
rootCauseRepaired: boolean(review.rootCauseRepaired, 'review.rootCauseRepaired'),
featurePreserved: boolean(review.featurePreserved, 'review.featurePreserved'),
boundaryMocks: boolean(review.boundaryMocks, 'review.boundaryMocks'),
rationale: string(review.rationale, 'review rationale'),
reviewedPatchHash: string(review.reviewedPatchHash, 'reviewedPatchHash'),
...(review.artifactId === undefined
? {}
: { artifactId: string(review.artifactId, 'review artifact ID') }),
},
};
if (!/^[a-f0-9]{32}$/.test(request.findingId))
throw new CsoError('INVALID_SCHEMA', 'findingId must be a helper-issued identifier');
if (request.review.artifactId !== undefined && !/^[a-f0-9]{32}$/.test(request.review.artifactId))
throw new CsoError('INVALID_SCHEMA', 'review artifact ID must be a helper-issued identifier');
if (!Number.isInteger(request.port) || request.port < 1024 || request.port > 65535)
throw new CsoError('INVALID_SCHEMA', 'port must be 1024..65535');
if (
!request.legitimate.length ||
!request.security.vulnerable ||
!request.existingTests.length ||
!request.boundaryFiles.length ||
!request.testFiles.length
)
throw new CsoError(
'INVALID_SCHEMA',
'Verification needs a legitimate control, distinct before/fixed security oracles, existing tests, immutable test files, and boundary files',
);
const secure = request.security.expected,
vulnerable = request.security.vulnerable;
const mutuallyExclusive =
secure.status !== vulnerable.status ||
(secure.includes !== undefined &&
vulnerable.excludes !== undefined &&
secure.includes.includes(vulnerable.excludes)) ||
(vulnerable.includes !== undefined &&
secure.excludes !== undefined &&
vulnerable.includes.includes(secure.excludes));
if (!mutuallyExclusive)
throw new CsoError(
'INVALID_SCHEMA',
'The vulnerable and fixed security oracles must be provably mutually exclusive',
);
if (new Set(request.changes.map((x) => x.path)).size !== request.changes.length)
throw new CsoError('INVALID_SCHEMA', 'Patch paths must be unique');
if (
new Set(request.testFiles).size !== request.testFiles.length ||
request.changes.some((change) => request.testFiles.includes(change.path))
)
throw new CsoError(
'INVALID_SCHEMA',
'Existing-test source files must be unique and unchanged by the repair',
);
if (
request.existingTests.some((command) =>
/(?:^|\/)(?:true|false|echo|printf|env|sh|bash)$/.test(command.executable),
)
)
throw new CsoError(
'INVALID_SCHEMA',
'Generic success or shell commands cannot stand in for a project test suite',
);
if (
!request.changes.some(
(change) =>
change.beforeSha256 === null || change.after === null || sha256(change.after) !== change.beforeSha256,
)
)
throw new CsoError('INVALID_SCHEMA', 'A tested repair must contain at least one material patch effect');
return request;
}
export function completeness(report: Pick<RunReportV3, 'coverage' | 'gaps'>): Completeness {
// Scanner adapters preserve operational outcomes, but scanner output is only
// candidate evidence. The corresponding investigation domain decides whether
// assessment work remains; an optional tool failure cannot override it.
// A successful snapshot is a prerequisite, not security assessment work by
// itself. Its helper-owned partial/not-assessed state remains material.
const work = report.coverage.filter(
(c) =>
c.status !== 'not_applicable' &&
!c.domain.startsWith('scanner:') &&
!(['snapshot-inputs', 'history-inputs'].includes(c.domain) && c.status === 'assessed'),
);
if (!report.gaps.length && work.length && work.every((c) => c.status === 'assessed')) return 'complete';
return work.some((c) => c.status === 'assessed' || c.status === 'partial') ? 'partial' : 'not assessed';
}
export function renderReport(report: RunReportV3): string {
const supported = report.findings.filter((f) => f.evidence === 'supported');
const gaps = [
...new Set([
...report.gaps,
...report.coverage.filter((c) => !c.domain.startsWith('scanner:')).flatMap((c) => c.gaps),
]),
];
const transformations = report.source.transformations ?? [];
// Report JSON is canonical evidence. Markdown is a safe plain-text view:
// collapse line breaks and escape all Markdown control characters so model,
// repository, scanner, and advisory strings cannot forge report structure.
const plain = (value: unknown): string =>
String(value)
.replace(/[\x00-\x1f\x7f-\x9f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]+/gu, ' ')
.replace(/\s{2,}/g, ' ')
.trim()
.replace(/[\\`*_[\]{}()#+!|<>]/g, '\\$&');
const list = (values: string[]): string => (values.length ? values.map(plain).join('; ') : 'none');
const terminal = [...report.events].reverse().find((item) => item.kind === 'terminal'),
startedAt = Date.parse(report.createdAt),
terminalAt = terminal ? Date.parse(terminal.at) : NaN;
const elapsed =
Number.isFinite(startedAt) && Number.isFinite(terminalAt) && terminalAt >= startedAt
? `; elapsed ${terminalAt - startedAt} ms`
: '';
const timing = `Timing: started ${plain(report.createdAt)}; deadline ${plain(report.deadline)}${terminal ? `; terminal ${plain(terminal.at)}${elapsed}` : ''}.`;
const usage = report.modelUsage
? `Model usage: ${report.modelUsage.tokens} host-reported tokens from ${plain(report.modelUsage.source)}${report.modelUsage.cost === undefined ? '' : `; host-reported cost ${report.modelUsage.cost}`}.`
: undefined;
const findingLines = (f: FindingV3): string[] => [
`- ${plain(f.severity.toUpperCase())} ${plain(f.title)} [${plain(f.id)}]`,
` Location: ${plain(f.location.path)}:${f.location.line} (${plain(f.location.symbol)}). Confidence: ${plain(f.confidence)} — ${plain(f.confidenceRationale)}. Evidence: ${plain(f.evidence)}.`,
` Attacker scenario: ${plain(f.scenario)}`,
` Attacker control: ${plain(f.attackerControl)}. Impact: ${plain(f.impact)}.`,
` Trace: ${list(f.trace)}. Supporting references: ${list(f.references)}.`,
` Counterevidence considered: ${plain(f.challenge.counterevidence)}. Challenge: ${plain(f.challenge.mode)} by ${plain(f.challenge.reviewer)}. Conclusion: ${plain(f.challenge.conclusion)}.`,
` Repair recommendation: ${plain(f.recommendation)}`,
` Reproduction: ${plain(f.reproduction)}${f.reproductionAttemptId ? ` (attempt ${plain(f.reproductionAttemptId)})` : ''}. Repair: ${plain(f.repair)}. Closure: ${plain(f.closure)}.`,
...(f.verificationId
? [
` Verification: ${plain(f.verificationId)}. Bundle: bundles/${plain(f.verificationId)}.json. Assertion assurance: ${plain(f.verificationAssurance?.assertions ?? 'unknown')}. Test completion assurance: ${plain(f.verificationAssurance?.testCompletion ?? 'unknown')}. Review assurance: ${plain(f.verificationAssurance?.review ?? 'unknown')}.`,
]
: []),
];
const model = report.application;
return [
`${report.completeness} — ${plain(report.policy.scope)}${report.policy.diff ? ` (diff against ${plain(report.policy.base)})` : ''}`,
`Run: ${plain(report.runId)}. Mode: ${plain(report.policy.mode)}.`,
timing,
...(usage ? [usage] : []),
`Material gaps: ${gaps.length ? list(gaps) : 'none reported'}.`,
'',
'Application model:',
`- Actors: ${list(model.actors)}.`,
`- Assets: ${list(model.assets)}.`,
`- Entrypoints: ${list(model.entrypoints)}.`,
`- Tenant boundaries: ${list(model.tenantBoundaries)}.`,
`- Sensitive operations: ${list(model.sensitiveOperations)}.`,
`- Security invariants: ${list(model.invariants)}.`,
'',
...(supported.length
? ['Supported findings:', ...supported.flatMap(findingLines)]
: ['No supported findings in the assessed scope.']),
...(report.policy.mode === 'comprehensive'
? [
'',
'Hypotheses (unconfirmed):',
...report.findings.filter((f) => f.evidence === 'hypothesis').flatMap(findingLines),
]
: []),
'',
'Snapshot transformations:',
...(transformations.length
? transformations.map((item) => `- ${plain(item.path)}: ${plain(item.handling)}`)
: ['- none']),
'',
'Coverage:',
...report.coverage.flatMap((c) => [
`- ${plain(c.domain)}: ${plain(c.status)}; ${plain(c.method)}${c.tool ? `; tool ${plain(c.tool.name)} ${plain(c.tool.version)}, freshness ${plain(c.tool.freshness)}, outcome ${plain(c.tool.outcome)}` : ''}.`,
` Scope: ${plain(c.scope)}. Evidence: ${list(c.evidence)}. Gaps: ${list(c.gaps)}. Exclusions: ${list(c.exclusions)}.`,
]),
'',
].join('\n');
}
type LegacyJson = null | boolean | number | string | LegacyJson[] | { [key: string]: LegacyJson };
function legacyJson(value: unknown, depth = 0, seen = new WeakSet<object>()): LegacyJson {
if (depth > 32) throw new CsoError('INVALID_SCHEMA', 'Legacy report nesting is too deep');
if (value === null || typeof value === 'boolean') return value;
if (typeof value === 'number') {
if (!Number.isFinite(value)) throw new CsoError('INVALID_SCHEMA', 'Legacy report numbers must be finite');
return value;
}
if (typeof value === 'string') {
if (value.length > MAX_OUTPUT || UNSAFE_STRING_CONTROLS.test(value))
throw new CsoError(
'INVALID_SCHEMA',
'Legacy report strings must be bounded and free of unsafe control characters',
);
return value;
}
if (!value || typeof value !== 'object')
throw new CsoError('INVALID_SCHEMA', 'Legacy report contains a non-JSON value');
if (seen.has(value)) throw new CsoError('INVALID_SCHEMA', 'Legacy report cannot be cyclic');
seen.add(value);
try {
if (Array.isArray(value)) {
if (value.length > 10_000) throw new CsoError('INVALID_SCHEMA', 'Legacy report array is too large');
return value.map((item) => legacyJson(item, depth + 1, seen));
}
const entries = Object.entries(value as Record<string, unknown>);
if (entries.length > 10_000) throw new CsoError('INVALID_SCHEMA', 'Legacy report object is too large');
const out: Record<string, LegacyJson> = Object.create(null);
for (const [key, item] of entries) {
if (
key.length > 1024 ||
['__proto__', 'prototype', 'constructor'].includes(key) ||
UNSAFE_PROPERTY_CONTROLS.test(key)
)
throw new CsoError('INVALID_SCHEMA', 'Legacy report contains an unsafe property');
out[key] = legacyJson(item, depth + 1, seen);
}
return out;
} finally {
seen.delete(value);
}
}
export function importLegacy(input: unknown): {
schemaVersion: 2;
readOnly: true;
findings: any[];
warning: string;
} {
const v = object(input, 'legacy report');
if (
!Array.isArray(v.findings) ||
![2, '2', '2.0', '2.0.0'].includes(v.schemaVersion ?? v.schema_version ?? v.version)
)
throw new CsoError('INVALID_SCHEMA', 'Expected a v2 report with findings');
return {
schemaVersion: 2,
readOnly: true,
warning:
'Legacy VERIFIED is review evidence only; it does not establish reproduction, tested repair, or closure.',
findings: v.findings.map((raw: any, index: number) => {
const f = object(raw, `legacy finding ${index + 1}`);
return {
title: typeof f.title === 'string' ? string(f.title, 'legacy title') : `Legacy finding ${index + 1}`,
status: typeof f.status === 'string' ? string(f.status, 'legacy status') : 'unknown',
...(typeof f.severity === 'string' ? { severity: string(f.severity, 'legacy severity') } : {}),
...(typeof f.description === 'string'
? { description: string(f.description, 'legacy description') }
: {}),
legacy: legacyJson(f),
evidence: 'legacy_review',
reproduction: 'not_attempted',
repair: 'not_attempted',
closure: 'unknown',
};
}),
};
}