71 lines
5.0 KiB
JavaScript
71 lines
5.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const policyPath = path.join(root, "docs/web/dependency-severity-policy.json");
|
|
const inventoryPath = path.join(root, "tests/golden/M13-05C/dependency-inventory.json");
|
|
const reportPath = path.join(root, "tests/golden/M13-05D/dependency-severity-report.json");
|
|
const manifestPath = path.join(root, "tests/golden/M13-05D/manifest.json");
|
|
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
const fileSha256 = (file) => sha256(fs.readFileSync(file));
|
|
const policy = JSON.parse(fs.readFileSync(policyPath, "utf8"));
|
|
const inventory = JSON.parse(fs.readFileSync(inventoryPath, "utf8"));
|
|
assert.equal(policy.schemaVersion, 1);
|
|
assert.equal(policy.task, "M13-05D");
|
|
assert.deepEqual(policy.severityOrder, ["LOW", "MEDIUM", "HIGH", "BLOCKER"]);
|
|
assert.deepEqual(policy.gates, { BLOCKER: "BLOCK", HIGH: "BLOCK", MEDIUM: "REVIEW", LOW: "TRACK" });
|
|
assert.deepEqual(policy.exceptionFields, ["owner", "expiresOn", "reason", "alternativeControl"]);
|
|
assert.deepEqual(policy.findings, []);
|
|
assert.deepEqual(policy.exceptions, []);
|
|
assert.equal(inventory.task, "M13-05C");
|
|
assert.ok(inventory.packages.length > 0);
|
|
|
|
const severityRank = new Map(policy.severityOrder.map((severity, index) => [severity, index]));
|
|
function validateException(exception, today = "2026-08-19") {
|
|
assert.equal(typeof exception.owner, "string");
|
|
assert.ok(exception.owner.trim().length > 0);
|
|
assert.match(exception.expiresOn, /^\d{4}-\d{2}-\d{2}$/u);
|
|
assert.ok(exception.expiresOn >= today, "exception is expired");
|
|
assert.equal(typeof exception.reason, "string");
|
|
assert.ok(exception.reason.trim().length > 0);
|
|
assert.equal(typeof exception.alternativeControl, "string");
|
|
assert.ok(exception.alternativeControl.trim().length > 0);
|
|
}
|
|
const accepted = { owner: "security@example.invalid", expiresOn: "2026-12-31", reason: "upstream patch window", alternativeControl: "network egress deny and pinned lockfile" };
|
|
validateException(accepted);
|
|
for (const invalid of [
|
|
{ ...accepted, owner: "" },
|
|
{ ...accepted, expiresOn: "2026-08-18" },
|
|
{ ...accepted, reason: "" },
|
|
{ ...accepted, alternativeControl: "" },
|
|
]) assert.throws(() => validateException(invalid));
|
|
function evaluate(findings, exceptions) {
|
|
const byId = new Map(exceptions.map((exception) => [exception.findingId, exception]));
|
|
const decisions = findings.map((finding) => {
|
|
assert.ok(severityRank.has(finding.severity));
|
|
const exception = byId.get(finding.id);
|
|
if (!exception) return { id: finding.id, severity: finding.severity, decision: policy.gates[finding.severity], exception: false };
|
|
validateException(exception);
|
|
return { id: finding.id, severity: finding.severity, decision: "EXCEPTION", exception: true };
|
|
});
|
|
const blocked = decisions.filter((decision) => decision.decision === "BLOCK");
|
|
const review = decisions.filter((decision) => decision.decision === "REVIEW");
|
|
return { decisions, blocked: blocked.length, review: review.length, status: blocked.length === 0 && review.length === 0 ? "PASS" : "BLOCKED" };
|
|
}
|
|
const clean = evaluate(policy.findings, policy.exceptions);
|
|
assert.deepEqual(clean, { decisions: [], blocked: 0, review: 0, status: "PASS" });
|
|
assert.equal(evaluate([{ id: "synthetic-high", severity: "HIGH" }], []).status, "BLOCKED");
|
|
assert.equal(evaluate([{ id: "synthetic-high", severity: "HIGH" }], [{ findingId: "synthetic-high", ...accepted }]).status, "PASS");
|
|
assert.equal(evaluate([{ id: "synthetic-medium", severity: "MEDIUM" }], []).status, "BLOCKED");
|
|
const report = { schemaVersion: 1, task: "M13-05D", operation: "DEPENDENCY_SEVERITY_POLICY", inventorySha256: fileSha256(inventoryPath), findings: policy.findings.length, exceptions: policy.exceptions.length, blocked: clean.blocked, review: clean.review, status: clean.status, negativeCases: { missingException: "BLOCKED", expiredException: "REJECTED", incompleteException: "REJECTED", completeException: "ACCEPTED" }, execution: "DISABLED", nextTask: "M13-05E" };
|
|
if (process.env.UPDATE_M13_05D_REPORT === "1") { fs.mkdirSync(path.dirname(reportPath), { recursive: true }); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); }
|
|
assert.deepEqual(JSON.parse(fs.readFileSync(reportPath, "utf8")), report);
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
assert.deepEqual({ schemaVersion: manifest.schemaVersion, task: manifest.task, parentTask: manifest.parentTask, nextTask: manifest.nextTask }, { schemaVersion: 1, task: "M13-05D", parentTask: "M13-05C", nextTask: "M13-05E" });
|
|
for (const artifact of Object.values(manifest.artifacts)) assert.equal(fileSha256(path.join(root, artifact.path)), artifact.sha256, artifact.path);
|
|
process.stdout.write("dependency-severity-policy-ok findings=0 exceptions=0 blocked=0 review=0 negativeCases=4 status=PASS execution=DISABLED next=M13-05E\n");
|