Add Cloudflare's security-audit skill
Vendored from cloudflare/security-audit-skill under .agents/skills, pinned in skills-lock.json and linked into .claude/skills. Also adds the project's shared permission allow-rules in .claude/settings.json. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Tk3nAVF6n4dtjQS17FRFr
This commit is contained in:
740
.agents/skills/security-audit/validate-coverage-ledger.test.cjs
Normal file
740
.agents/skills/security-audit/validate-coverage-ledger.test.cjs
Normal file
@@ -0,0 +1,740 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const test = require("node:test");
|
||||
const {
|
||||
LIMITS,
|
||||
canonicalCoverageId,
|
||||
encodeCanonicalRef,
|
||||
isSafeAgentId,
|
||||
isSafeRelativePath,
|
||||
preflightJsonText,
|
||||
validateDocument,
|
||||
} = require("./validate-coverage-ledger.cjs");
|
||||
|
||||
const validatorPath = path.join(__dirname, "validate-coverage-ledger.cjs");
|
||||
const CLI_TIMEOUT_MS = 5000;
|
||||
const HOSTILE_CLI_TIMEOUT_MS = 15000;
|
||||
const HAS_SAFE_INPUT_OPEN = Number.isInteger(fs.constants.O_NOFOLLOW) &&
|
||||
fs.constants.O_NOFOLLOW !== 0 &&
|
||||
Number.isInteger(fs.constants.O_NONBLOCK) &&
|
||||
fs.constants.O_NONBLOCK !== 0;
|
||||
|
||||
function unit(overrides = {}) {
|
||||
const canonicalRefs = overrides.canonical_refs || {
|
||||
surface: "src/router.ts#POST /users/:id",
|
||||
boundary: "src/authz.ts#requireOwner",
|
||||
subsystem: "packages/api",
|
||||
attack_class: "ATTACK-CLASSES.md#Access control",
|
||||
};
|
||||
const value = {
|
||||
coverage_id: canonicalCoverageId(canonicalRefs),
|
||||
canonical_refs: canonicalRefs,
|
||||
surface: "Update-user route",
|
||||
boundary: "Object ownership",
|
||||
subsystem: "API",
|
||||
attack_class: "Access control",
|
||||
starting_paths: ["src/router.ts", "src/authz.ts"],
|
||||
ordinary_attack_class_block: "ATTACK-CLASSES.md#Access control",
|
||||
selected_companion_blocks: [],
|
||||
excluded_blocks: [{ block: "WEB-PROTOCOL-AND-AUTH.md#Cache behavior", reason: "The route is not cached." }],
|
||||
prior_status: "new",
|
||||
attempts: [],
|
||||
wave: 1,
|
||||
status: "planned",
|
||||
agent_id: null,
|
||||
reviewed_paths: [],
|
||||
local_checks: [],
|
||||
result_fingerprints: [],
|
||||
unresolved: [],
|
||||
};
|
||||
return Object.assign(value, overrides, { canonical_refs: canonicalRefs });
|
||||
}
|
||||
|
||||
function errorsFor(value) {
|
||||
return validateDocument(value);
|
||||
}
|
||||
|
||||
function runCli(contents, options = {}) {
|
||||
const { nodeArgs = [], timeout = CLI_TIMEOUT_MS } = options;
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "validate-coverage-ledger-"));
|
||||
const ledgerPath = path.join(directory, "coverage-ledger.json");
|
||||
try {
|
||||
fs.writeFileSync(ledgerPath, contents);
|
||||
return spawnSync(process.execPath, [...nodeArgs, validatorPath, ledgerPath], {
|
||||
encoding: "utf8",
|
||||
timeout,
|
||||
});
|
||||
} finally {
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function cliOutput(result) {
|
||||
return `${result.stdout}${result.stderr}`;
|
||||
}
|
||||
|
||||
const TERMINAL_CONTROL_PAYLOAD = "\u001b\u0007\u0085\u202e";
|
||||
const TERMINAL_CONTROL_BYTES = [
|
||||
Buffer.from([0x1b]),
|
||||
Buffer.from([0x07]),
|
||||
Buffer.from("\u0085"),
|
||||
Buffer.from("\u202e"),
|
||||
];
|
||||
|
||||
function assertNoInjectedControlBytes(output) {
|
||||
const bytes = Buffer.isBuffer(output) ? output : Buffer.from(output, "utf8");
|
||||
for (const marker of TERMINAL_CONTROL_BYTES) {
|
||||
assert.equal(bytes.indexOf(marker), -1, `found raw control bytes ${marker.toString("hex")}`);
|
||||
}
|
||||
}
|
||||
|
||||
function sourceCheck(agentId = "hunter-1", overrides = {}) {
|
||||
return {
|
||||
agent_id: agentId,
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
invariant: "The route checks object ownership.",
|
||||
method: "source",
|
||||
result: "The owner check applies before the update.",
|
||||
artifact: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function localCheck(agentId = "hunter-1", overrides = {}) {
|
||||
return sourceCheck(agentId, {
|
||||
method: "local",
|
||||
result: "The bounded fixture accepted the other owner's object.",
|
||||
artifact: `agents/${agentId}/artifacts/result.txt`,
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
function archivedAttempt(overrides = {}) {
|
||||
const value = {
|
||||
wave: 1,
|
||||
status: "blocked",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
result_fingerprints: [],
|
||||
unresolved: ["The deployed policy is unavailable."],
|
||||
reassignment_reason: "The critic found an unchecked parallel path.",
|
||||
};
|
||||
return Object.assign(value, overrides);
|
||||
}
|
||||
|
||||
test("accepts an empty ledger and complete units", () => {
|
||||
assert.deepEqual(errorsFor([]), []);
|
||||
assert.deepEqual(errorsFor([unit()]), []);
|
||||
|
||||
const missingAttempts = unit();
|
||||
delete missingAttempts.attempts;
|
||||
assert(errorsFor([missingAttempts]).some((error) => error.includes('missing required field "attempts"')));
|
||||
|
||||
const covered = unit({
|
||||
status: "covered",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
});
|
||||
assert.deepEqual(errorsFor([covered]), []);
|
||||
});
|
||||
|
||||
test("accepts a complete ledger through the CLI", { skip: !HAS_SAFE_INPUT_OPEN }, () => {
|
||||
const result = runCli(JSON.stringify([unit()]));
|
||||
assert.equal(result.status, 0, cliOutput(result));
|
||||
assert.match(result.stdout, /PASS: 1 coverage units valid/);
|
||||
});
|
||||
|
||||
test("text preflight ignores structural characters and escapes inside strings", () => {
|
||||
const value = unit({
|
||||
surface: "Route \\ slash [list] {object}, colon: quoted \"value\"",
|
||||
excluded_blocks: [{
|
||||
block: "COMPANION.md#Literal [brackets] {braces}",
|
||||
reason: "The text contains a backslash \\ before an escaped \"quote\".",
|
||||
}],
|
||||
});
|
||||
const contents = JSON.stringify([value]);
|
||||
assert.doesNotThrow(() => preflightJsonText(contents));
|
||||
assert.deepEqual(JSON.parse(contents), [value]);
|
||||
if (HAS_SAFE_INPUT_OPEN) {
|
||||
const result = runCli(contents);
|
||||
assert.equal(result.status, 0, cliOutput(result));
|
||||
}
|
||||
});
|
||||
|
||||
test("text preflight enforces structural cardinality limits", () => {
|
||||
const depthLimit = LIMITS.nestingDepth;
|
||||
assert.doesNotThrow(() => preflightJsonText(`${"[".repeat(depthLimit)}0${"]".repeat(depthLimit)}`));
|
||||
assert.throws(
|
||||
() => preflightJsonText(`${"[".repeat(depthLimit + 1)}0${"]".repeat(depthLimit + 1)}`),
|
||||
/exceeds nesting depth limit 64/,
|
||||
);
|
||||
|
||||
const tooManyUnits = `[${"null,".repeat(LIMITS.units)}null]`;
|
||||
assert.throws(() => preflightJsonText(tooManyUnits), /exceeds 10000 top-level unit limit/);
|
||||
|
||||
const tooManyItems = `[[${"null,".repeat(LIMITS.collectionItems)}null]]`;
|
||||
assert.throws(() => preflightJsonText(tooManyItems), /exceeds 1000 item array limit/);
|
||||
|
||||
const objectFields = Array.from(
|
||||
{ length: LIMITS.objectFields + 1 },
|
||||
(_, index) => `"field${index}":null`,
|
||||
).join(",");
|
||||
assert.throws(() => preflightJsonText(`[{${objectFields}}]`), /exceeds 1000 field object limit/);
|
||||
|
||||
const fullArray = `[${"null,".repeat(LIMITS.collectionItems - 1)}null]`;
|
||||
const arraysNeeded = Math.floor(LIMITS.preflightValues / (LIMITS.collectionItems + 1)) + 1;
|
||||
const tooManyValues = `[${Array.from({ length: arraysNeeded }, () => fullArray).join(",")}]`;
|
||||
assert.throws(() => preflightJsonText(tooManyValues), /exceeds 500000 total value limit/);
|
||||
});
|
||||
|
||||
test("text preflight rejects malformed structural truncation cleanly", () => {
|
||||
assert.throws(() => preflightJsonText("["), /truncated JSON structure/);
|
||||
assert.throws(() => preflightJsonText("[\"unterminated"), /unterminated JSON string/);
|
||||
assert.throws(() => preflightJsonText("[{\"field\":1]"), /mismatched JSON containers/);
|
||||
});
|
||||
|
||||
test("derives collision-free canonical IDs from exact UTF-8 references", () => {
|
||||
assert.equal(encodeCanonicalRef("route:POST /users"), "route%3APOST%20%2Fusers");
|
||||
assert.notEqual(encodeCanonicalRef("route name"), encodeCanonicalRef("route-name"));
|
||||
assert.equal(
|
||||
canonicalCoverageId({ surface: "a", boundary: "b", subsystem: "c", attack_class: "d", lifecycle: "retry" }),
|
||||
"a::b::c::d::retry",
|
||||
);
|
||||
assert.throws(() => encodeCanonicalRef("e\u0301"), /invalid canonical reference/);
|
||||
assert.throws(() => encodeCanonicalRef("bad\u0000ref"), /invalid canonical reference/);
|
||||
assert.throws(() => encodeCanonicalRef("hidden\u200bref"), /invalid canonical reference/);
|
||||
});
|
||||
|
||||
test("rejects noncanonical, duplicate, and colliding IDs", () => {
|
||||
const wrong = unit({ coverage_id: "display-label-slug" });
|
||||
assert(errorsFor([wrong]).some((error) => error.includes("expected canonical ID")));
|
||||
|
||||
const duplicate = unit();
|
||||
assert(errorsFor([duplicate, unit()]).some((error) => error.includes("duplicate coverage ID")));
|
||||
|
||||
const collision = unit();
|
||||
const differentMeaning = unit({ surface: "Delete-user route" });
|
||||
assert(errorsFor([collision, differentMeaning]).some((error) => error.includes("canonical identity collision")));
|
||||
});
|
||||
|
||||
test("requires canonical references to be own properties", () => {
|
||||
const inherited = Object.create(unit().canonical_refs);
|
||||
const value = unit();
|
||||
value.canonical_refs = inherited;
|
||||
assert(errorsFor([value]).some((error) => error.includes("missing required field")));
|
||||
});
|
||||
|
||||
test("rejects aliases for one semantic tuple", () => {
|
||||
const first = unit();
|
||||
const refs = { ...first.canonical_refs, surface: "src/alias.ts#updateUser" };
|
||||
const alias = unit({ canonical_refs: refs });
|
||||
const ledger = [first, alias].sort((left, right) => left.coverage_id.localeCompare(right.coverage_id));
|
||||
assert(errorsFor(ledger).some((error) => error.includes("semantic tuple already uses coverage ID")));
|
||||
});
|
||||
|
||||
test("requires lexicographic order", () => {
|
||||
const secondRefs = {
|
||||
surface: "zzz",
|
||||
boundary: "src/authz.ts#requireOwner",
|
||||
subsystem: "packages/api",
|
||||
attack_class: "ATTACK-CLASSES.md#Access control",
|
||||
};
|
||||
assert(errorsFor([unit({ canonical_refs: secondRefs }), unit()])
|
||||
.some((error) => error.includes("sorted lexicographically")));
|
||||
});
|
||||
|
||||
test("validates assignment block maps", () => {
|
||||
const overlap = unit({
|
||||
selected_companion_blocks: ["AI-AND-LLM.md#Tool calls"],
|
||||
excluded_blocks: [{ block: "AI-AND-LLM.md#Tool calls", reason: "Claimed irrelevant." }],
|
||||
});
|
||||
assert(errorsFor([overlap]).some((error) => error.includes("also selected")));
|
||||
|
||||
const noReason = unit({ excluded_blocks: [{ block: "AI-AND-LLM.md#Tool calls", reason: "" }] });
|
||||
assert(errorsFor([noReason]).some((error) => error.includes("reason")));
|
||||
});
|
||||
|
||||
test("requires owned artifacts for local checks and null artifacts for source checks", () => {
|
||||
const local = unit({
|
||||
status: "covered",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [localCheck()],
|
||||
});
|
||||
assert.deepEqual(errorsFor([local]), []);
|
||||
|
||||
const independentlyVerified = unit({
|
||||
status: "covered",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts", "src/authz.ts"],
|
||||
local_checks: [sourceCheck(), localCheck("verifier-1", { reviewed_paths: ["src/authz.ts"] })],
|
||||
});
|
||||
assert.deepEqual(errorsFor([independentlyVerified]), []);
|
||||
|
||||
const unownedPath = unit({
|
||||
status: "covered",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts", "src/authz.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
});
|
||||
assert(errorsFor([unownedPath]).some((error) => error.includes("has no check owner")));
|
||||
|
||||
for (const [checkAgentId, artifact] of [
|
||||
[null, "agents/hunter-1/artifacts/result.txt"],
|
||||
["hunter-1", null],
|
||||
["hunter-1", "result.txt"],
|
||||
["hunter-1", "agents/hunter-2/artifacts/result.txt"],
|
||||
["../hunter", "agents/../hunter/artifacts/result.txt"],
|
||||
]) {
|
||||
const value = unit({
|
||||
status: "covered",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [localCheck(checkAgentId, { artifact })],
|
||||
});
|
||||
assert.notEqual(errorsFor([value]).length, 0, `${checkAgentId}: ${artifact}`);
|
||||
}
|
||||
|
||||
const unownedSource = unit({
|
||||
status: "blocked",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
unresolved: ["The boundary behavior is not source-visible."],
|
||||
});
|
||||
assert(errorsFor([unownedSource]).some((error) => error.includes("unit with status \"blocked\" requires a canonical lowercase agent ID")));
|
||||
|
||||
const sourceWithArtifact = unit({
|
||||
status: "covered",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck("hunter-1", { artifact: "agents/hunter-1/artifacts/source.txt" })],
|
||||
});
|
||||
assert(errorsFor([sourceWithArtifact]).some((error) => error.includes("source-only check must use null")));
|
||||
});
|
||||
|
||||
test("requires canonical lowercase filesystem-safe agent IDs", () => {
|
||||
for (const value of ["hunter-1", "verifier_2", "a0"]) assert.equal(isSafeAgentId(value), true, value);
|
||||
for (const value of ["Hunter-1", "hunter.1", "hunter-1.", "hunter ", "con", "prn", "aux", "nul", "com1", "lpt9", "../hunter"]) {
|
||||
assert.equal(isSafeAgentId(value), false, value);
|
||||
}
|
||||
|
||||
const caseAlias = unit({ status: "in_progress", agent_id: "Hunter-1" });
|
||||
assert(errorsFor([caseAlias]).some((error) => error.includes("canonical lowercase agent ID")));
|
||||
});
|
||||
|
||||
test("enforces state evidence", () => {
|
||||
assert(errorsFor([unit({ status: "in_progress" })]).some((error) => error.includes("unit with status \"in_progress\" requires")));
|
||||
assert(errorsFor([unit({ status: "blocked" })]).some((error) => error.includes("unresolved")));
|
||||
assert(errorsFor([unit({ status: "candidate" })]).some((error) => error.includes("reviewed_paths")));
|
||||
|
||||
assert.deepEqual(errorsFor([unit({ status: "in_progress", agent_id: "hunter-1" })]), []);
|
||||
const inProgressEvidence = unit({
|
||||
status: "in_progress",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
});
|
||||
assert(errorsFor([inProgressEvidence]).some((error) => error.includes("must keep this array empty")));
|
||||
|
||||
const assignedPlanned = unit({
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
});
|
||||
assert(errorsFor([assignedPlanned]).some((error) => error.includes("planned unit must be unassigned")));
|
||||
|
||||
const candidate = unit({
|
||||
status: "candidate",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck("hunter-1", { invariant: "Ownership is required.", result: "No check exists." })],
|
||||
result_fingerprints: ["src-router-missing-owner-check"],
|
||||
unresolved: ["validation_budget_exhausted"],
|
||||
});
|
||||
assert.deepEqual(errorsFor([candidate]), []);
|
||||
|
||||
const blocked = unit({
|
||||
status: "blocked",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
unresolved: ["The deployed policy is unavailable."],
|
||||
});
|
||||
assert.deepEqual(errorsFor([blocked]), []);
|
||||
const blockedFingerprint = { ...blocked, result_fingerprints: ["forbidden-fingerprint"] };
|
||||
assert(errorsFor([blockedFingerprint]).some((error) => error.includes("result_fingerprints")));
|
||||
|
||||
for (const status of ["not_applicable", "out_of_scope", "deferred"]) {
|
||||
assert.deepEqual(errorsFor([unit({ status, unresolved: ["Reason recorded."] })]), []);
|
||||
const invalid = unit({
|
||||
status,
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
result_fingerprints: ["forbidden-fingerprint"],
|
||||
unresolved: ["Reason recorded."],
|
||||
});
|
||||
const errors = errorsFor([invalid]);
|
||||
assert(errors.some((error) => error.includes("must be unassigned")), status);
|
||||
assert(errors.some((error) => error.includes("reviewed_paths")), status);
|
||||
assert(errors.some((error) => error.includes("result_fingerprints")), status);
|
||||
}
|
||||
|
||||
const coveredFingerprint = unit({
|
||||
status: "covered",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
result_fingerprints: ["forbidden-fingerprint"],
|
||||
});
|
||||
assert(errorsFor([coveredFingerprint]).some((error) => error.includes("result_fingerprints")));
|
||||
|
||||
const coveredUnresolved = unit({
|
||||
status: "covered",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck()],
|
||||
unresolved: ["Unexpected unresolved claim."],
|
||||
});
|
||||
assert(errorsFor([coveredUnresolved]).some((error) => error.includes("unresolved")));
|
||||
});
|
||||
|
||||
test("archives prior evidence when a critic assigns a fresh owner", () => {
|
||||
const reassigned = unit({
|
||||
attempts: [archivedAttempt()],
|
||||
wave: 2,
|
||||
status: "in_progress",
|
||||
agent_id: "hunter-2",
|
||||
});
|
||||
assert.deepEqual(errorsFor([reassigned]), []);
|
||||
|
||||
const finalClosure = unit({
|
||||
attempts: [archivedAttempt()],
|
||||
wave: 2,
|
||||
status: "covered",
|
||||
agent_id: "hunter-2",
|
||||
reviewed_paths: ["src/authz.ts"],
|
||||
local_checks: [sourceCheck("hunter-2", { reviewed_paths: ["src/authz.ts"] })],
|
||||
});
|
||||
assert.deepEqual(errorsFor([finalClosure]), []);
|
||||
});
|
||||
|
||||
test("preserves candidate provenance when reassignment must be deferred", () => {
|
||||
const candidateAttempt = archivedAttempt({
|
||||
status: "candidate",
|
||||
result_fingerprints: ["src-router-missing-owner-check"],
|
||||
unresolved: ["validation_budget_exhausted"],
|
||||
});
|
||||
const deferred = unit({
|
||||
attempts: [candidateAttempt],
|
||||
wave: 2,
|
||||
status: "deferred",
|
||||
unresolved: ["quick_profile_final_critic"],
|
||||
});
|
||||
assert.deepEqual(errorsFor([deferred]), []);
|
||||
});
|
||||
|
||||
test("rejects reassignment owner reuse and evidence mixing", () => {
|
||||
const reusedOwner = unit({
|
||||
attempts: [archivedAttempt()],
|
||||
wave: 2,
|
||||
status: "in_progress",
|
||||
agent_id: "hunter-1",
|
||||
});
|
||||
assert(errorsFor([reusedOwner]).some((error) => error.includes("current assignment owner must be fresh")));
|
||||
|
||||
const mixedEvidence = unit({
|
||||
attempts: [archivedAttempt({ local_checks: [localCheck()] })],
|
||||
wave: 2,
|
||||
status: "covered",
|
||||
agent_id: "hunter-2",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [localCheck()],
|
||||
});
|
||||
const errors = errorsFor([mixedEvidence]);
|
||||
assert(errors.some((error) => error.includes("prior assignment owner evidence must remain")));
|
||||
assert(errors.some((error) => error.includes("artifact from an archived attempt cannot be reused")));
|
||||
|
||||
const mixedHistory = unit({
|
||||
attempts: [
|
||||
archivedAttempt({ local_checks: [localCheck()] }),
|
||||
archivedAttempt({
|
||||
wave: 2,
|
||||
agent_id: "hunter-2",
|
||||
local_checks: [localCheck()],
|
||||
}),
|
||||
],
|
||||
wave: 3,
|
||||
status: "in_progress",
|
||||
agent_id: "hunter-3",
|
||||
});
|
||||
const historyErrors = errorsFor([mixedHistory]);
|
||||
assert(historyErrors.some((error) => error.includes("prior assignment owner evidence must remain in its earlier attempt")));
|
||||
assert(historyErrors.some((error) => error.includes("artifact from an earlier attempt cannot be reused")));
|
||||
|
||||
const unordered = unit({
|
||||
attempts: [archivedAttempt(), archivedAttempt({
|
||||
wave: 1,
|
||||
agent_id: "hunter-2",
|
||||
local_checks: [sourceCheck("hunter-2")],
|
||||
})],
|
||||
wave: 3,
|
||||
status: "in_progress",
|
||||
agent_id: "hunter-3",
|
||||
});
|
||||
assert(errorsFor([unordered]).some((error) => error.includes("strictly increasing")));
|
||||
});
|
||||
|
||||
test("rejects unsafe paths and malformed fingerprints", () => {
|
||||
for (const value of [
|
||||
"/etc/passwd",
|
||||
"../src/file.js",
|
||||
"src/../file.js",
|
||||
"src/con.txt",
|
||||
"src/PRN",
|
||||
"src/AUX.c",
|
||||
"src/NUL",
|
||||
"src/CLOCK$.txt",
|
||||
"src/conin$.txt",
|
||||
"src/conout$",
|
||||
"src/COM1.log",
|
||||
"src/lpt9",
|
||||
"src/COM\u00b9.log",
|
||||
"src/COM\u00b2.log",
|
||||
"src/COM\u00b3.log",
|
||||
"src/lpt\u00b9",
|
||||
"src/lpt\u00b2",
|
||||
"src/lpt\u00b3",
|
||||
"src/file.js.",
|
||||
"C:/src/file.js",
|
||||
"src/file\n.js",
|
||||
"src/file\u0085.js",
|
||||
"src/file\u2028.js",
|
||||
"src/file\u200b.js",
|
||||
"src/file\u034f.js",
|
||||
"src/file\ufe0f.js",
|
||||
]) {
|
||||
assert.equal(isSafeRelativePath(value), false, value);
|
||||
}
|
||||
assert.equal(isSafeRelativePath("src/handler.js"), true);
|
||||
assert.equal(isSafeRelativePath("src/caf\u00e9/handler.js"), true);
|
||||
assert(errorsFor([unit({ starting_paths: ["../src/router.ts"] })]).some((error) => error.includes("repository-relative path")));
|
||||
assert(errorsFor([unit({
|
||||
status: "candidate",
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: ["src/router.ts"],
|
||||
local_checks: [sourceCheck("hunter-1", { invariant: "Ownership is required.", result: "No check exists." })],
|
||||
result_fingerprints: ["not stable"],
|
||||
})]).some((error) => error.includes("invalid fingerprint")));
|
||||
});
|
||||
|
||||
test("rejects format, default-ignorable, and invalid-scalar prose", () => {
|
||||
for (const invisible of ["\u200b", "\u034f", "\ufe0f", "\ud800"]) {
|
||||
assert(errorsFor([unit({ surface: invisible })]).some((error) => error.includes("surface")), JSON.stringify(invisible));
|
||||
assert(errorsFor([unit({
|
||||
excluded_blocks: [{ block: "ATTACK-CLASSES.md#Access control", reason: invisible }],
|
||||
})]).some((error) => error.includes("reason")), JSON.stringify(invisible));
|
||||
}
|
||||
});
|
||||
|
||||
test("quotes input-derived controls in direct validation errors", () => {
|
||||
const invalidStatus = `invalid-${TERMINAL_CONTROL_PAYLOAD}`;
|
||||
const invalidPath = `src/${TERMINAL_CONTROL_PAYLOAD}.js`;
|
||||
const value = unit({
|
||||
status: invalidStatus,
|
||||
agent_id: "hunter-1",
|
||||
reviewed_paths: [invalidPath],
|
||||
local_checks: [sourceCheck()],
|
||||
result_fingerprints: ["force-state-error"],
|
||||
});
|
||||
const output = errorsFor([value]).join("\n");
|
||||
|
||||
assert.match(output, /\$\[0\]\.status/);
|
||||
assert.match(output, /\$\[0\]\.reviewed_paths/);
|
||||
assert.match(output, /\\u001b/);
|
||||
assert.match(output, /\\u0007/);
|
||||
assert.match(output, /\\u0085/);
|
||||
assert.match(output, /\\u202e/);
|
||||
assertNoInjectedControlBytes(output);
|
||||
});
|
||||
|
||||
test("quotes input-derived controls in CLI validation errors", { skip: !HAS_SAFE_INPUT_OPEN }, () => {
|
||||
const value = unit({
|
||||
status: `invalid-${TERMINAL_CONTROL_PAYLOAD}`,
|
||||
result_fingerprints: ["force-state-error"],
|
||||
});
|
||||
const result = runCli(JSON.stringify([value]));
|
||||
|
||||
assert.equal(result.status, 1, cliOutput(result));
|
||||
assert.match(result.stderr, /\$\[0\]\.status/);
|
||||
assert.match(result.stderr, /\\u001b/);
|
||||
assertNoInjectedControlBytes(result.stderr);
|
||||
});
|
||||
|
||||
test("returns a generic syntax error without parser-supplied controls", { skip: !HAS_SAFE_INPUT_OPEN }, () => {
|
||||
const malformed = Buffer.concat([
|
||||
Buffer.from("["),
|
||||
Buffer.from(TERMINAL_CONTROL_PAYLOAD),
|
||||
Buffer.from("]"),
|
||||
]);
|
||||
const result = runCli(malformed);
|
||||
|
||||
assert.equal(result.status, 1, cliOutput(result));
|
||||
assert.equal(result.stderr, "Failed to parse coverage ledger: invalid JSON syntax\n");
|
||||
assertNoInjectedControlBytes(result.stderr);
|
||||
});
|
||||
|
||||
test("does not reflect controls from a failed CLI input path", () => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "validate-coverage-ledger-path-"));
|
||||
const missingPath = path.join(directory, `missing-${TERMINAL_CONTROL_PAYLOAD}.json`);
|
||||
try {
|
||||
const result = spawnSync(process.execPath, [validatorPath, missingPath], {
|
||||
encoding: "utf8",
|
||||
timeout: CLI_TIMEOUT_MS,
|
||||
});
|
||||
assert.equal(result.status, 1, cliOutput(result));
|
||||
assert.match(result.stderr, /Failed to read coverage ledger:/);
|
||||
assertNoInjectedControlBytes(result.stderr);
|
||||
} finally {
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects invalid UTF-8 through the CLI", { skip: !HAS_SAFE_INPUT_OPEN }, () => {
|
||||
const encoded = Buffer.from(JSON.stringify([unit()]));
|
||||
const marker = Buffer.from("Update-user route");
|
||||
const markerOffset = encoded.indexOf(marker);
|
||||
assert.notEqual(markerOffset, -1);
|
||||
const malformed = Buffer.concat([
|
||||
encoded.subarray(0, markerOffset),
|
||||
Buffer.from([0x80]),
|
||||
encoded.subarray(markerOffset + marker.length),
|
||||
]);
|
||||
|
||||
const result = runCli(malformed);
|
||||
const output = cliOutput(result);
|
||||
assert.equal(result.status, 1, output);
|
||||
assert.match(output, /input is not valid UTF-8/);
|
||||
assert.doesNotMatch(output, /TypeError|stack|at validate-coverage-ledger/i);
|
||||
});
|
||||
|
||||
test("rejects a FIFO through the CLI without blocking", { skip: process.platform === "win32" || !HAS_SAFE_INPUT_OPEN }, () => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "validate-coverage-ledger-fifo-"));
|
||||
const fifoPath = path.join(directory, "coverage-ledger.json");
|
||||
try {
|
||||
const created = spawnSync("mkfifo", [fifoPath], { encoding: "utf8", timeout: CLI_TIMEOUT_MS });
|
||||
assert.equal(created.status, 0, cliOutput(created));
|
||||
|
||||
const result = spawnSync(process.execPath, [validatorPath, fifoPath], {
|
||||
encoding: "utf8",
|
||||
timeout: CLI_TIMEOUT_MS,
|
||||
});
|
||||
const output = cliOutput(result);
|
||||
assert.notEqual(result.error && result.error.code, "ETIMEDOUT", output);
|
||||
assert.equal(result.status, 1, output);
|
||||
assert.match(output, /input must be a regular file/);
|
||||
assert.doesNotMatch(output, /stack|at validate-coverage-ledger/i);
|
||||
} finally {
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects a symlink through the CLI", { skip: process.platform === "win32" || !HAS_SAFE_INPUT_OPEN }, () => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "validate-coverage-ledger-symlink-"));
|
||||
const targetPath = path.join(directory, "target.json");
|
||||
const symlinkPath = path.join(directory, "coverage-ledger.json");
|
||||
try {
|
||||
fs.writeFileSync(targetPath, JSON.stringify([unit()]));
|
||||
fs.symlinkSync(targetPath, symlinkPath);
|
||||
const result = spawnSync(process.execPath, [validatorPath, symlinkPath], {
|
||||
encoding: "utf8",
|
||||
timeout: CLI_TIMEOUT_MS,
|
||||
});
|
||||
const output = cliOutput(result);
|
||||
assert.notEqual(result.error && result.error.code, "ETIMEDOUT", output);
|
||||
assert.equal(result.status, 1, output);
|
||||
assert.match(output, /input must not be a symlink/);
|
||||
assert.doesNotMatch(output, /stack|at validate-coverage-ledger/i);
|
||||
} finally {
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects deeply nested input without recursion failure", () => {
|
||||
let nested = 0;
|
||||
for (let depth = 0; depth < 20000; depth++) nested = [nested];
|
||||
assert(errorsFor(nested).some((error) => error.includes("exceeds nesting depth limit 64")));
|
||||
if (!HAS_SAFE_INPUT_OPEN) return;
|
||||
|
||||
const result = runCli(`${"[".repeat(20000)}0${"]".repeat(20000)}`);
|
||||
const output = cliOutput(result);
|
||||
assert.notEqual(result.error && result.error.code, "ETIMEDOUT", output);
|
||||
assert.equal(result.status, 1, output);
|
||||
assert.match(output, /exceeds nesting depth limit 64/);
|
||||
assert.doesNotMatch(output, /RangeError|Maximum call stack|stack|at validate-coverage-ledger/i);
|
||||
});
|
||||
|
||||
test("rejects multi-megabyte nesting under a constrained Node heap", { skip: !HAS_SAFE_INPUT_OPEN }, () => {
|
||||
const openContainers = "[".repeat(2000000);
|
||||
const cases = [
|
||||
openContainers,
|
||||
`${openContainers}0${"]".repeat(2000000)}`,
|
||||
];
|
||||
for (const contents of cases) {
|
||||
const result = runCli(contents, {
|
||||
nodeArgs: ["--max-old-space-size=64"],
|
||||
timeout: HOSTILE_CLI_TIMEOUT_MS,
|
||||
});
|
||||
const output = cliOutput(result);
|
||||
assert.notEqual(result.error && result.error.code, "ETIMEDOUT", output);
|
||||
assert.equal(result.status, 1, output);
|
||||
assert.match(output, /exceeds nesting depth limit 64/);
|
||||
assert.doesNotMatch(output, /heap out of memory|allocation failed|RangeError|Maximum call stack|stack|at validate-coverage-ledger/i);
|
||||
}
|
||||
});
|
||||
|
||||
test("caps malformed 10000-unit validation output", () => {
|
||||
assert.equal(errorsFor(Array.from({ length: LIMITS.units }, () => null)).length, LIMITS.validationErrors);
|
||||
if (!HAS_SAFE_INPUT_OPEN) return;
|
||||
|
||||
const result = runCli(JSON.stringify(Array.from({ length: LIMITS.units }, () => null)));
|
||||
const output = cliOutput(result);
|
||||
assert.notEqual(result.error && result.error.code, "ETIMEDOUT", output);
|
||||
assert.equal(result.status, 1, output);
|
||||
assert.match(output, /output capped at 100/);
|
||||
assert(output.length < 20000, `unexpected output length ${output.length}`);
|
||||
assert.doesNotMatch(output, /RangeError|Maximum call stack|stack|at validate-coverage-ledger/i);
|
||||
});
|
||||
|
||||
test("rejects malformed top-level data and excessive unit counts", () => {
|
||||
assert.deepEqual(errorsFor({ units: [] }), ["$: expected a top-level array"]);
|
||||
const tooMany = Array.from({ length: 10001 }, () => null);
|
||||
const errors = errorsFor(tooMany);
|
||||
assert.deepEqual(errors, ["$: exceeds 10000 coverage units"]);
|
||||
|
||||
const oversizedCollection = unit({ extra: Array.from({ length: LIMITS.collectionItems + 1 }, () => null) });
|
||||
assert(errorsFor([oversizedCollection]).some((error) => error.includes("exceeds 1000 entries")));
|
||||
});
|
||||
|
||||
test("accepts a canonical ID derived from near-maximum multibyte references", () => {
|
||||
const canonicalRefs = {
|
||||
surface: "\u6f22".repeat(1024),
|
||||
boundary: "\u00e9".repeat(1024),
|
||||
subsystem: "packages/api",
|
||||
attack_class: "\u6f22".repeat(1023) + "\u00e9",
|
||||
};
|
||||
const value = unit({ canonical_refs: canonicalRefs });
|
||||
assert(value.coverage_id.length > 16384, `coverage_id length ${value.coverage_id.length}`);
|
||||
assert(value.coverage_id.length <= 65536, `coverage_id length ${value.coverage_id.length}`);
|
||||
assert.deepEqual(errorsFor([value]), []);
|
||||
|
||||
if (HAS_SAFE_INPUT_OPEN) {
|
||||
const result = runCli(JSON.stringify([value]));
|
||||
assert.equal(result.status, 0, cliOutput(result));
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user