Initialize Browsec architecture and TrustLab
This commit is contained in:
commit
a1e56c1d09
14 files changed
+1500
No files matched your search
@@ -0,0 +1,181 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
createBuiltinFinalHandler,
|
||||
DecisionJournal,
|
||||
TrustRunner,
|
||||
} from "../src/index.js";
|
||||
import {
|
||||
unknownLocalAuthority,
|
||||
validPublicCertificate,
|
||||
} from "../fixtures/tls.js";
|
||||
|
||||
test("built-in handler rejects an unresolved certificate failure", async () => {
|
||||
const result = await new TrustRunner().evaluate(unknownLocalAuthority);
|
||||
assert.equal(result.verdict.trusted, false);
|
||||
assert.equal(result.journal.entries.length, 0);
|
||||
});
|
||||
|
||||
test("built-in handler preserves an ordinary successful validation", async () => {
|
||||
const result = await new TrustRunner().evaluate(validPublicCertificate);
|
||||
assert.equal(result.verdict.trusted, true);
|
||||
});
|
||||
|
||||
test("a decision plugin can trust a narrowly scoped connection", async () => {
|
||||
const plugin = pluginWith({
|
||||
decide({ facts }) {
|
||||
return {
|
||||
trusted: true,
|
||||
scope: { hostname: facts.hostname, port: facts.port },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const result = await new TrustRunner({ plugins: [plugin] }).evaluate(
|
||||
unknownLocalAuthority,
|
||||
);
|
||||
assert.equal(result.verdict.trusted, true);
|
||||
assert.equal(result.journal.entries.at(-1).kind, "resolution");
|
||||
assert.equal(result.journal.entries.at(-1).pluginId, plugin.manifest.id);
|
||||
});
|
||||
|
||||
test("evidence collection is independent but appended in plugin order", async () => {
|
||||
const slowFirst = pluginWith({
|
||||
id: "test.first",
|
||||
async collectEvidence() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
return { entries: [{ kind: "evidence", code: "first" }] };
|
||||
},
|
||||
});
|
||||
const fastSecond = pluginWith({
|
||||
id: "test.second",
|
||||
collectEvidence() {
|
||||
return { entries: [{ kind: "warning", code: "second" }] };
|
||||
},
|
||||
});
|
||||
|
||||
const result = await new TrustRunner({ plugins: [slowFirst, fastSecond] }).evaluate(
|
||||
validPublicCertificate,
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.journal.entries.map((entry) => entry.code),
|
||||
["first", "second"],
|
||||
);
|
||||
});
|
||||
|
||||
test("plugin timeout is recorded and falls through safely", async () => {
|
||||
const plugin = pluginWith({
|
||||
async decide() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
|
||||
const result = await new TrustRunner({ plugins: [plugin], timeoutMs: 5 }).evaluate(
|
||||
unknownLocalAuthority,
|
||||
);
|
||||
assert.equal(result.verdict.trusted, false);
|
||||
assert.equal(result.journal.entries.at(-1).code, "plugin-timeout");
|
||||
});
|
||||
|
||||
test("a verdict cannot escape the active hostname and port", async () => {
|
||||
const plugin = pluginWith({
|
||||
decide() {
|
||||
return {
|
||||
trusted: true,
|
||||
scope: { hostname: "different.test", port: 443 },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const result = await new TrustRunner({ plugins: [plugin] }).evaluate(
|
||||
unknownLocalAuthority,
|
||||
);
|
||||
assert.equal(result.verdict.trusted, false);
|
||||
assert.match(result.journal.entries.at(-1).message, /scope does not match/);
|
||||
});
|
||||
|
||||
test("observer and advisor plugins cannot issue terminal verdicts", async () => {
|
||||
for (const role of ["observer", "advisor"]) {
|
||||
const plugin = pluginWith({
|
||||
role,
|
||||
decide({ facts }) {
|
||||
return {
|
||||
trusted: true,
|
||||
scope: { hostname: facts.hostname, port: facts.port },
|
||||
};
|
||||
},
|
||||
});
|
||||
const result = await new TrustRunner({ plugins: [plugin] }).evaluate(
|
||||
unknownLocalAuthority,
|
||||
);
|
||||
assert.equal(result.verdict.trusted, false);
|
||||
assert.match(result.journal.entries.at(-1).message, /cannot issue/);
|
||||
}
|
||||
});
|
||||
|
||||
test("veto authority can reject but cannot trust", async () => {
|
||||
const plugin = pluginWith({
|
||||
role: "veto-authority",
|
||||
decide({ facts }) {
|
||||
return {
|
||||
trusted: true,
|
||||
scope: { hostname: facts.hostname, port: facts.port },
|
||||
};
|
||||
},
|
||||
});
|
||||
const result = await new TrustRunner({ plugins: [plugin] }).evaluate(
|
||||
unknownLocalAuthority,
|
||||
);
|
||||
assert.equal(result.verdict.trusted, false);
|
||||
assert.match(result.journal.entries.at(-1).message, /cannot issue a trusted/);
|
||||
});
|
||||
|
||||
test("journal snapshots and entries are immutable", () => {
|
||||
const journal = new DecisionJournal();
|
||||
journal.append(
|
||||
{ id: "test.plugin", name: "Test plugin" },
|
||||
[{ kind: "evidence", data: { nested: true } }],
|
||||
);
|
||||
const snapshot = journal.snapshot();
|
||||
|
||||
assert.throws(() => snapshot.entries.push({}), TypeError);
|
||||
assert.throws(() => {
|
||||
snapshot.entries[0].data.nested = false;
|
||||
}, TypeError);
|
||||
});
|
||||
|
||||
test("only Browsec's immutable handler can occupy the final position", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
new TrustRunner({
|
||||
finalHandler: {
|
||||
manifest: {
|
||||
id: "test.impostor",
|
||||
name: "Impostor",
|
||||
role: "decision-authority",
|
||||
},
|
||||
finalize() {},
|
||||
},
|
||||
}),
|
||||
/must be Browsec's built-in handler/,
|
||||
);
|
||||
|
||||
assert.doesNotThrow(() =>
|
||||
new TrustRunner({ finalHandler: createBuiltinFinalHandler() }),
|
||||
);
|
||||
});
|
||||
|
||||
function pluginWith({
|
||||
id = "test.plugin",
|
||||
role = "decision-authority",
|
||||
collectEvidence,
|
||||
decide,
|
||||
}) {
|
||||
return {
|
||||
manifest: { id, name: id, role },
|
||||
collectEvidence,
|
||||
decide,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user