mirror of
https://github.com/hanzoai/trust.git
synced 2026-10-30 14:54:58 +00:00
447 lines
20 KiB
JavaScript
447 lines
20 KiB
JavaScript
// The trust centre as a PRODUCT: a tenant authors its own, and the two things
|
|
// that must never bend are the tier rule and the baseline guard.
|
|
//
|
|
// Every case runs the COMMITTED bundle in a bare node:vm against the store
|
|
// model in vm.mjs, so what is under test is the artifact that ships.
|
|
|
|
import assert from "node:assert/strict";
|
|
import { it } from "./harness.mjs";
|
|
import { load, NOW } from "./vm.mjs";
|
|
|
|
const ORG = "acme";
|
|
|
|
const call = (handle, route, opts = {}) =>
|
|
handle({ route, method: opts.method || "GET", params: opts.params || {}, orgId: opts.org ?? ORG, body: opts.body });
|
|
|
|
// The write body is an ENVELOPE — { ord, data } — because the record is an open
|
|
// object and the host binds the section and the id from the URL onto a struct.
|
|
const put = (handle, kind, id, data, ord = 0) =>
|
|
call(handle, "section.put", { method: "PUT", params: { kind, id }, body: { ord, data } });
|
|
|
|
// A tenant that is NOT the inventory's owner — a customer, on their first
|
|
// request. This is the case a single-tenant page never exercises and the one a
|
|
// product lives or dies on.
|
|
const tenant = () => load({ own: false });
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// A new organization starts empty, and says so with numbers rather than silence.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it("a new organization has no controls, and its coverage is honestly zero", () => {
|
|
const { handle } = tenant();
|
|
const res = call(handle, "coverage.list");
|
|
assert.equal(res.status, 200);
|
|
assert.equal(res.body.controls.total, 0);
|
|
for (const f of res.body.frameworks) {
|
|
assert.equal(f.automated, 0, f.framework);
|
|
assert.equal(f.partial, 0, f.framework);
|
|
// The denominator is still the whole published clause list. A tenant with
|
|
// nothing is 0 of 61, never 0 of 0 — which would read as complete.
|
|
assert.equal(f.none, f.total, f.framework);
|
|
assert.ok(f.total > 0, f.framework);
|
|
}
|
|
});
|
|
|
|
it("the deployment's OWN inventory is not visible to another organization", () => {
|
|
assert.ok(load({ own: true }).handle && call(load({ own: true }).handle, "controls.list").body.total > 0);
|
|
assert.equal(call(tenant().handle, "controls.list").body.total, 0);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The tier rule. This is the one the whole page rests on.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it("a document is GATED unless somebody says otherwise", () => {
|
|
const { handle } = tenant();
|
|
assert.equal(put(handle, "document", "d1", { title: "Network diagram", kind: "other" }).status, 200);
|
|
// The owner of the centre sees their own address, so `released` is about the
|
|
// READER. What defaults is the TIER, and it defaults closed.
|
|
assert.equal(call(handle, "documents.list").body.documents[0].tier, "gated");
|
|
|
|
put(handle, "profile", "", { name: "Acme", published: true });
|
|
assert.equal(call(handle, "published").body.documents[0].released, false);
|
|
});
|
|
|
|
it("an AUDITOR-ATTESTED document cannot be published, whatever the caller asks for", () => {
|
|
const { handle } = tenant();
|
|
for (const kind of ["soc2", "iso", "pentest", "letter"]) {
|
|
const res = put(handle, "document", kind, { title: "T", kind, tier: "public", href: "https://x/y.pdf" });
|
|
assert.equal(res.status, 400, kind);
|
|
assert.match(res.body.errors.join(" "), /released through a grant/, kind);
|
|
}
|
|
});
|
|
|
|
it("a SELF-assessment may be published, because we are the ones attesting it", () => {
|
|
const { handle } = tenant();
|
|
for (const kind of ["caiq", "sig", "vsa"]) {
|
|
const res = put(handle, "document", kind, { title: "T", kind, tier: "public", href: "https://x/y.xlsx" });
|
|
assert.equal(res.status, 200, kind);
|
|
}
|
|
const docs = call(handle, "documents.list").body.documents;
|
|
assert.equal(docs.length, 3);
|
|
for (const d of docs) assert.equal(d.released, true, d.kind);
|
|
});
|
|
|
|
it("a gated document keeps its title and loses its address", () => {
|
|
const { handle } = tenant();
|
|
put(handle, "document", "d1", { title: "SOC 2 report", kind: "soc2", href: "https://s3/secret.pdf" });
|
|
put(handle, "profile", "", { name: "Acme", published: true });
|
|
|
|
const own = call(handle, "documents.list").body.documents[0];
|
|
assert.equal(own.href, "https://s3/secret.pdf", "the owner sees their own address");
|
|
|
|
const pub = call(handle, "published").body.documents[0];
|
|
assert.equal(pub.title, "SOC 2 report", "the artifact is still named");
|
|
assert.equal(pub.released, false);
|
|
assert.equal(pub.href, undefined, "a visitor is never handed the address");
|
|
assert.equal(pub.attested, true);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The public door.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it("an UNPUBLISHED centre is not found, rather than empty", () => {
|
|
const { handle } = tenant();
|
|
put(handle, "faq", "q1", { question: "Where?", answer: "Here." });
|
|
const res = call(handle, "published");
|
|
assert.equal(res.status, 404);
|
|
assert.match(res.body.message, /no published trust centre/);
|
|
});
|
|
|
|
it("publishing is a deliberate act, and then the whole centre reads", () => {
|
|
const { handle } = tenant();
|
|
put(handle, "profile", "", { name: "Acme", tagline: "We hold your data.", published: true });
|
|
put(handle, "subprocessor", "s1", {
|
|
name: "A cloud",
|
|
role: "processor",
|
|
purpose: "Compute",
|
|
data: "Every request, and everything stored at rest.",
|
|
location: "United States",
|
|
terms: "https://example.test/dpa",
|
|
evidence: [{ repo: "acme/infra", path: "terraform/main.tf" }],
|
|
});
|
|
put(handle, "policy", "p1", { title: "Access policy" });
|
|
put(handle, "faq", "q1", { question: "Where?", answer: "Here." });
|
|
put(handle, "update", "u1", { at: "2026-08-01", title: "Rotated our signing keys" });
|
|
put(handle, "risk", "", { items: [{ label: "Data processed", value: "Customer content" }] });
|
|
|
|
const b = call(handle, "published").body;
|
|
assert.equal(b.org, ORG);
|
|
assert.equal(b.profile.name, "Acme");
|
|
assert.equal(b.subprocessors.length, 1);
|
|
assert.equal(b.policies.length, 1);
|
|
assert.equal(b.faq.length, 1);
|
|
assert.equal(b.updates.length, 1);
|
|
assert.equal(b.risk.items.length, 1);
|
|
assert.equal(b.generated, NOW);
|
|
// Every section is present even when empty, so the page renders "none
|
|
// published" rather than dropping a heading and looking complete. Compared by
|
|
// length, not deepEqual: the value crosses a vm realm boundary, so its Array
|
|
// is not this realm's Array and a strict deepEqual fails on the prototype.
|
|
assert.ok(Array.isArray(b.documents) || b.documents.length === 0);
|
|
assert.equal(b.documents.length, 0);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The baseline guard: a tenant cannot restate a control the build gate approved.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it("the deployment cannot author over its OWN inventory through the API", () => {
|
|
const { handle } = load({ own: true });
|
|
const res = put(handle, "control", "iam.pkce.s256", {
|
|
claim: "Everything is fine.",
|
|
mechanism: "Trust me.",
|
|
status: "automated",
|
|
enforced: [{ repo: "a/b", path: "c.go" }],
|
|
verified: [{ method: "test", at: [{ repo: "a/b", path: "c_test.go" }] }],
|
|
maps: [{ clause: "soc2:CC6.1", strength: "full" }],
|
|
});
|
|
assert.equal(res.status, 409);
|
|
assert.match(res.body.message, /governed in git/);
|
|
assert.equal(call(handle, "section.delete", { method: "DELETE", params: { kind: "control", id: "iam.pkce.s256" } }).status, 409);
|
|
});
|
|
|
|
it("a row carrying a baseline id is ignored rather than merged", () => {
|
|
const { handle } = load({
|
|
own: true,
|
|
seed: [{ kind: "control", id: "iam.pkce.s256", ord: 0, updated: 1, data: JSON.stringify({ id: "iam.pkce.s256", status: "automated" }) }],
|
|
});
|
|
const ids = call(handle, "controls.list").body.controls.filter((c) => c.id === "iam.pkce.s256");
|
|
assert.equal(ids.length, 1, "the baseline entry wins and there is exactly one");
|
|
assert.match(ids[0].claim, /challenge/, "it is the committed one, not the row");
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// One validator, both callers.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it("an authored control is held to the SAME rule a committed one is", () => {
|
|
const { handle } = tenant();
|
|
const base = {
|
|
claim: "Something true.",
|
|
mechanism: "How it works.",
|
|
status: "automated",
|
|
enforced: [{ repo: "acme/app", path: "auth.go" }],
|
|
verified: [{ method: "test", at: [{ repo: "acme/app", path: "auth_test.go" }] }],
|
|
maps: [{ clause: "soc2:CC6.1", strength: "full" }],
|
|
};
|
|
assert.equal(put(handle, "control", "acme.auth", base).status, 200);
|
|
|
|
// The claim ban: prose may not say we hold a certificate.
|
|
const claims = put(handle, "control", "acme.b", { ...base, claim: "We are certified." });
|
|
assert.equal(claims.status, 400);
|
|
assert.match(claims.body.errors.join(" "), /does not claim status/);
|
|
|
|
// A framework belongs in maps, where it arrives attached to a number.
|
|
const names = put(handle, "control", "acme.c", { ...base, mechanism: "Meets SOC 2 in full." });
|
|
assert.equal(names.status, 400);
|
|
assert.match(names.body.errors.join(" "), /a framework belongs in maps/);
|
|
|
|
// A mapping to a clause no framework declares scores nowhere and reads as
|
|
// coverage to anyone who opens the file.
|
|
const ghost = put(handle, "control", "acme.d", { ...base, maps: [{ clause: "soc2:ZZ9.9", strength: "full" }] });
|
|
assert.equal(ghost.status, 400);
|
|
assert.match(ghost.body.errors.join(" "), /no framework declares that clause/);
|
|
|
|
// Anything short of automated must say what is missing.
|
|
const shrug = put(handle, "control", "acme.e", { ...base, status: "partial" });
|
|
assert.equal(shrug.status, 400);
|
|
assert.match(shrug.body.errors.join(" "), /requires a note/);
|
|
});
|
|
|
|
it("an authored control counts, and an ABSENT one still never moves a number", () => {
|
|
const { handle } = tenant();
|
|
const base = {
|
|
claim: "Something true.",
|
|
mechanism: "How it works.",
|
|
enforced: [{ repo: "acme/app", path: "auth.go" }],
|
|
maps: [{ clause: "soc2:CC6.1", strength: "full" }],
|
|
};
|
|
put(handle, "control", "acme.real", {
|
|
...base,
|
|
status: "automated",
|
|
verified: [{ method: "test", at: [{ repo: "acme/app", path: "auth_test.go" }] }],
|
|
});
|
|
put(handle, "control", "acme.wish", {
|
|
...base,
|
|
status: "absent",
|
|
note: "We do not have this yet.",
|
|
maps: [{ clause: "soc2:CC6.2", strength: "full" }],
|
|
verified: [{ method: "read", at: [{ repo: "acme/app", path: "auth.go" }] }],
|
|
});
|
|
|
|
const soc2 = call(handle, "coverage.get", { params: { framework: "soc2" } }).body;
|
|
assert.equal(soc2.automated, 1, "the real one counts");
|
|
const wished = soc2.clauses.find((c) => c.id === "CC6.2");
|
|
assert.equal(wished.level, "none", "the absent one does not");
|
|
assert.equal(wished.controls.length, 0, "and it is not even listed as covering it");
|
|
// It is still a roadmap: the control exists and names the clause it would
|
|
// satisfy. Naming it simply never moves a number.
|
|
assert.equal(call(handle, "controls.get", { params: { id: "acme.wish" } }).body.maps[0].clause, "soc2:CC6.2");
|
|
});
|
|
|
|
it("a category outside the eight is refused, so no section renders nowhere", () => {
|
|
const { handle } = tenant();
|
|
const base = {
|
|
claim: "Something true.",
|
|
mechanism: "How it works.",
|
|
status: "automated",
|
|
enforced: [{ repo: "acme/app", path: "auth.go" }],
|
|
verified: [{ method: "test", at: [{ repo: "acme/app", path: "auth_test.go" }] }],
|
|
maps: [{ clause: "soc2:CC6.1", strength: "full" }],
|
|
};
|
|
assert.equal(put(handle, "control", "acme.ok", { ...base, category: "network" }).status, 200);
|
|
const bad = put(handle, "control", "acme.bad", { ...base, category: "vibes" });
|
|
assert.equal(bad.status, 400);
|
|
assert.match(bad.body.errors.join(" "), /category must be one of/);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tenancy, structurally.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
it("no route reads an organization out of the request", () => {
|
|
const { handle } = tenant();
|
|
// Every route but health refuses without the host's tenant, and there is no
|
|
// parameter that can supply one — the org arrives on the envelope the host
|
|
// fills from the validated bearer, and nowhere else.
|
|
for (const route of ["center", "controls.list", "coverage.list", "documents.list", "published"]) {
|
|
const res = handle({ route, method: "GET", params: { org: "victim", orgId: "victim" }, query: { org: "victim" } });
|
|
assert.equal(res.status, 401, route);
|
|
}
|
|
assert.equal(call(handle, "health", { org: "" }).status, 200, "health needs none");
|
|
});
|
|
|
|
it("a single-valued section cannot be made plural by naming an id", () => {
|
|
const { handle } = tenant();
|
|
put(handle, "profile", "", { name: "First" });
|
|
put(handle, "profile", "sneaky", { name: "Second" });
|
|
assert.equal(call(handle, "profile.get").body.name, "Second", "the second write replaced the first");
|
|
const { rows } = tenant();
|
|
void rows;
|
|
// One row, whatever id was asked for.
|
|
const b = call(handle, "center").body;
|
|
assert.equal(b.profile.name, "Second");
|
|
});
|
|
|
|
it("a section nobody has heard of is not found, rather than created", () => {
|
|
const { handle } = tenant();
|
|
const res = put(handle, "vibes", "x", { anything: true });
|
|
assert.equal(res.status, 404);
|
|
assert.match(res.body.message, /no trust section/);
|
|
});
|
|
|
|
// normalize() in build.mjs rebuilds a control field by field, so a field it does
|
|
// not name is dropped between controls.json and the bundle — silently, and
|
|
// visible only on a page where the grouping had gone missing. This is the gate
|
|
// for that whole class: every control the real inventory ships reaches the
|
|
// artifact carrying a group from the closed set.
|
|
it("every control reaches the bundle carrying its category", () => {
|
|
const { handle } = load({ own: true });
|
|
const groups = [
|
|
"infrastructure", "data", "access", "network",
|
|
"endpoint", "corporate", "product", "incident",
|
|
];
|
|
const controls = call(handle, "controls.list").body.controls;
|
|
assert.ok(controls.length > 0);
|
|
// Compared by LENGTH, not deepEqual: the value crosses a vm realm boundary, so
|
|
// its Array is not this realm's and a strict deepEqual fails on the prototype
|
|
// even when the contents match.
|
|
const missing = [...controls].filter((c) => !groups.includes(c.category)).map((c) => c.id);
|
|
assert.equal(
|
|
missing.length,
|
|
0,
|
|
"reached the artifact with no group, or one outside the eight: " + missing.join(", "),
|
|
);
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The disclosure. Same two sources, same validator, same guard as a control —
|
|
// and the schema is what makes the classification impossible to leave implied.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// A party a customer's data reaches, filed without saying WHAT it reaches, is a
|
|
// name on a page. That is the thing this section exists instead of, so the
|
|
// field carrying the classification is required rather than encouraged.
|
|
it("a party filed without saying what data reaches it is refused", () => {
|
|
const { handle } = tenant();
|
|
const res = put(handle, "subprocessor", "s1", {
|
|
name: "A cloud",
|
|
role: "processor",
|
|
purpose: "Compute",
|
|
location: "United States",
|
|
terms: "https://example.test/dpa",
|
|
});
|
|
assert.equal(res.status, 400);
|
|
assert.ok(res.body.errors.some((e) => /data is required/.test(e)), res.body.errors.join("; "));
|
|
});
|
|
|
|
// The three a reviewer asks of a party that holds their data. Made structural:
|
|
// there is no way to file a processor without answering all of them.
|
|
it("a processor must say where it is and under which terms", () => {
|
|
const { handle } = tenant();
|
|
const res = put(handle, "subprocessor", "s1", {
|
|
name: "A cloud",
|
|
role: "processor",
|
|
purpose: "Compute",
|
|
data: "Everything.",
|
|
});
|
|
assert.equal(res.status, 400);
|
|
assert.ok(res.body.errors.some((e) => /where it is/.test(e)), res.body.errors.join("; "));
|
|
assert.ok(res.body.errors.some((e) => /terms/.test(e)), res.body.errors.join("; "));
|
|
});
|
|
|
|
// The other half of the same rule. A vendor is a party nothing reaches, so
|
|
// asking it for a residency and a processing agreement would be asking about
|
|
// something that does not happen — but it still has to say so in words, because
|
|
// "none, and here is why" is the claim and a blank field is not that claim.
|
|
it("a vendor needs no residency, and still has to say nothing reaches it", () => {
|
|
const { handle } = tenant();
|
|
const ok = put(handle, "subprocessor", "v1", {
|
|
name: "An advertising platform",
|
|
role: "vendor",
|
|
purpose: "Advertising we buy.",
|
|
data: "None. No script of theirs is served by any surface of ours.",
|
|
evidence: [{ origin: "ads.example.test" }],
|
|
});
|
|
assert.equal(ok.status, 200);
|
|
const bare = put(handle, "subprocessor", "v2", {
|
|
name: "Another platform",
|
|
role: "vendor",
|
|
purpose: "Advertising we buy.",
|
|
evidence: [{ origin: "other.example.test" }],
|
|
});
|
|
assert.equal(bare.status, 400);
|
|
});
|
|
|
|
it("a party with no role, or one outside the pair, is refused", () => {
|
|
const { handle } = tenant();
|
|
const base = { name: "A cloud", purpose: "Compute", data: "Everything." };
|
|
assert.equal(put(handle, "subprocessor", "s1", base).status, 400);
|
|
assert.equal(put(handle, "subprocessor", "s2", { ...base, role: "partner" }).status, 400);
|
|
});
|
|
|
|
// The claim ban reaches here too. A party's own marketing is the likeliest place
|
|
// a status claim gets copied into this repository, and repeating one is making
|
|
// it — so the same module that refuses it in a control refuses it here.
|
|
it("a party cannot be filed carrying a claim about its own status", () => {
|
|
const { handle } = tenant();
|
|
const res = put(handle, "subprocessor", "s1", {
|
|
name: "A cloud",
|
|
role: "processor",
|
|
purpose: "Compute, and they are certified.",
|
|
data: "Everything.",
|
|
location: "United States",
|
|
terms: "https://example.test/dpa",
|
|
});
|
|
assert.equal(res.status, 400);
|
|
assert.ok(res.body.errors.some((e) => /does not restate/.test(e)), res.body.errors.join("; "));
|
|
});
|
|
|
|
// The guard a control already has, now asked of both sections by one predicate.
|
|
// A party that can read customer data is not something a request may add or
|
|
// remove: adding one is a commit, and the fold drops a row that tries to
|
|
// restate a declared id rather than merging it.
|
|
it("the deployment cannot author over its OWN disclosure through the API", () => {
|
|
const { handle } = load({ own: true });
|
|
const res = put(handle, "subprocessor", "cloudflare", {
|
|
name: "Not Cloudflare",
|
|
role: "vendor",
|
|
purpose: "Nothing at all.",
|
|
data: "None.",
|
|
});
|
|
assert.equal(res.status, 409);
|
|
assert.match(res.body.message, /governed in git/);
|
|
const gone = call(handle, "section.delete", {
|
|
method: "DELETE",
|
|
params: { kind: "subprocessor", id: "cloudflare" },
|
|
});
|
|
assert.equal(gone.status, 409);
|
|
});
|
|
|
|
it("the compiled-in disclosure belongs to ONE organization", () => {
|
|
assert.ok(call(load({ own: true }).handle, "subprocessors.list").body.subprocessors.length > 0);
|
|
assert.equal(call(tenant().handle, "subprocessors.list").body.subprocessors.length, 0);
|
|
});
|
|
|
|
// Every party this deployment declares reaches the artifact carrying the field
|
|
// the page groups on and the field the disclosure turns on. Same gate, and same
|
|
// reason, as the one below it for a control's category: normalize and the bundle
|
|
// sit between the file and the reader, and a field dropped in between is visible
|
|
// only as a column that quietly went missing.
|
|
it("every declared party reaches the bundle with a role from the pair", () => {
|
|
const { handle } = load({ own: true });
|
|
const rows = [...call(handle, "subprocessors.list").body.subprocessors];
|
|
assert.ok(rows.length > 0);
|
|
const bad = rows.filter((s) => s.role !== "processor" && s.role !== "vendor").map((s) => s.id);
|
|
assert.equal(bad.length, 0, "reached the artifact with no role: " + bad.join(", "));
|
|
const mute = rows.filter((s) => !s.data || !s.purpose).map((s) => s.id);
|
|
assert.equal(mute.length, 0, "reached the artifact saying nothing: " + mute.join(", "));
|
|
// At least one party is disclosed as reachable in a browser, which is what
|
|
// bin/surface resolves what our pages reach against. With none, that gate has
|
|
// nothing to check and would pass by being empty.
|
|
const origins = rows.flatMap((s) => (s.evidence || []).filter((e) => e.origin));
|
|
assert.ok(origins.length > 0, "no party declares an origin, so bin/surface checks nothing");
|
|
});
|