diff --git a/action/finding-evidence-bounds.test.cts b/action/finding-evidence-bounds.test.cts new file mode 100644 index 0000000..91726ac --- /dev/null +++ b/action/finding-evidence-bounds.test.cts @@ -0,0 +1,38 @@ +"use strict"; + +const assert = require("node:assert/strict"); +const test = require("node:test"); +const { validateFindingEvidenceBounds } = require("./post.cts"); + +function evidence(retained: number, sampled: number, truncated: boolean): any { + return { + findings: Array.from({ length: retained }, () => ({})), + findings_truncated: truncated, + counters: { + total_violations: sampled, + sampled_violations: sampled, + }, + }; +} + +test("accepts resident finding evidence at producer boundaries", () => { + assert.equal(validateFindingEvidenceBounds(evidence(0, 0, false)).findings.length, 0); + assert.equal(validateFindingEvidenceBounds(evidence(1, 1, false)).findings.length, 1); + assert.equal(validateFindingEvidenceBounds(evidence(1024, 1024, false)).findings.length, 1024); + assert.equal(validateFindingEvidenceBounds(evidence(1024, 1025, true)).findings.length, 1024); +}); + +test("rejects resident finding evidence the producer cannot emit", () => { + for (const invalid of [ + { ...evidence(0, 0, false), findings: "not-an-array" }, + evidence(1025, 1025, false), + evidence(1023, 1024, true), + evidence(1, 2, false), + evidence(1024, 1024, true), + ]) { + assert.throws( + () => validateFindingEvidenceBounds(invalid), + /bounded network findings/, + ); + } +}); diff --git a/action/post.cts b/action/post.cts index 56b8acd..feb25f3 100644 --- a/action/post.cts +++ b/action/post.cts @@ -36,6 +36,7 @@ const MANIFEST = path.join(ACTION_ROOT, "bundle-manifest.json"); const EVIDENCE_SETTLE_INTERVAL_MILLISECONDS = 40; const EVIDENCE_SETTLE_MAX_READS = 4; const EVIDENCE_SETTLE_TIMEOUT_NANOSECONDS = 160_000_000n; +const MAX_RETAINED_FINDINGS = 1024; const CHILD_ENV = { LANG: "C.UTF-8", LC_ALL: "C.UTF-8", @@ -99,6 +100,31 @@ function networkEvidenceCounters(report: any): { total: number; sampled: number }; } +function validateFindingEvidenceBounds(report: any): any { + const findings = report.findings; + const counters = networkEvidenceCounters(report); + if ( + !Array.isArray(findings) || + findings.length > MAX_RETAINED_FINDINGS || + typeof report.findings_truncated !== "boolean" || + ( + report.findings_truncated && + findings.length !== MAX_RETAINED_FINDINGS + ) || + ( + !report.findings_truncated && + counters.sampled !== findings.length + ) || + ( + report.findings_truncated && + counters.sampled <= findings.length + ) + ) { + throw new Error("Fence resident report does not contain bounded network findings"); + } + return report; +} + function settleResidentReport( reportPath: string, unit: string, @@ -221,7 +247,9 @@ function main(): void { readJsonBounded(reportPath, MAX_REPORT_BYTES, "Fence report"), false, ); - const report = settleResidentReport(reportPath, paths.unit, initialReport); + const report = validateFindingEvidenceBounds( + settleResidentReport(reportPath, paths.unit, initialReport), + ); let dnsEvidence; const effectiveDnsReportPath = dnsReportPath || paths.dnsReport; if (fs.existsSync(effectiveDnsReportPath)) { @@ -318,7 +346,7 @@ function main(): void { for (const warning of resultsStorageWarnings(dnsEvidence)) { log.warning(warning); } - validateReport(report, true); + validateFindingEvidenceBounds(validateReport(report, true)); const auditDestinationCount = auditSummary.hostnameRows.length + auditSummary.ipRows.length; const evidenceLine = log.postEvidenceLine(report, auditDestinationCount); if (evidenceLine) { @@ -337,4 +365,4 @@ if (require.main === module) { } } -module.exports = { main, settleResidentReport }; +module.exports = { main, settleResidentReport, validateFindingEvidenceBounds }; diff --git a/script/test-action-wrapper b/script/test-action-wrapper index 3c60132..313e41c 100755 --- a/script/test-action-wrapper +++ b/script/test-action-wrapper @@ -7,7 +7,7 @@ source script/env "$@" require_cmd node node -e 'require("./action/lib.cts"); require("./action/main.cts"); require("./action/post.cts")' -node --test action/test.cts +node --test action/test.cts action/finding-evidence-bounds.test.cts python3 -B -E script/lib/host_observation.py --self-test python3 -B -E script/lib/action_bundle_host.py --self-test script/test-action-bundle-provenance