feat: isolate privacy scanning and release signing

This commit is contained in:
2026-09-02 10:07:01 +03:00
parent 847e6e4da9
commit 8592581b18
15 changed files with 2128 additions and 1 deletions

View File

@@ -0,0 +1,8 @@
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
LABEL org.opencontainers.image.title="portfolio-privacy-matcher" \
org.opencontainers.image.description="Decoder-free independent privacy evidence matcher"
WORKDIR /opt/matcher
COPY containers/privacy-matcher/matcher.mjs /opt/matcher/matcher.mjs
USER 65532:65532
ENTRYPOINT ["node", "/opt/matcher/matcher.mjs"]

View File

@@ -0,0 +1,49 @@
import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
const request = JSON.parse(await readFile("/input/request.json", "utf8"));
const [a, b] = request.reports ?? [];
if (
!a ||
!b ||
a.workerId !== "worker-a" ||
b.workerId !== "worker-b" ||
a.imageDigest === b.imageDigest ||
a.toolchain === b.toolchain ||
a.inputDigest !== b.inputDigest ||
a.checkId !== b.checkId ||
a.normalizedOutputDigest !== b.normalizedOutputDigest ||
JSON.stringify(a.normalizedMembers) !== JSON.stringify(b.normalizedMembers) ||
a.verdict !== "pass" ||
b.verdict !== "pass"
)
throw new Error("Independent worker evidence does not agree.");
const rules = request.rules;
if (!Array.isArray(rules) || rules.length === 0)
throw new Error("Matcher rules are required.");
const appliedRuleIds = rules
.map((rule) => rule.id)
.sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
if (
new Set(appliedRuleIds).size !== appliedRuleIds.length ||
rules.some((rule) => !Array.isArray(rule.deniedMemberDigests))
)
throw new Error("Matcher rules are invalid.");
const denied = new Set(rules.flatMap((rule) => rule.deniedMemberDigests));
if (a.normalizedMembers.some((member) => denied.has(member)))
throw new Error("Privacy rule matched normalized evidence.");
const result = {
schema: "portfolio-privacy-matcher-result/v1",
inputDigest: a.inputDigest,
checkId: a.checkId,
normalizedOutputDigest: a.normalizedOutputDigest,
normalizedMembers: a.normalizedMembers,
ruleSetDigest: request.ruleSetDigest,
appliedRuleIds,
verdict: "pass",
workerImageDigests: [a.imageDigest, b.imageDigest],
};
await writeFile("/output/result.json", JSON.stringify(result), {
flag: "wx",
mode: 0o400,
});