feat: harden bridge origin/CSP, cache decryption, and overhaul the web client UI

This commit is contained in:
randogoth 2026-09-26 19:04:36 +03:00
parent f637d5be57
commit 80133da100
10 changed files with 975 additions and 352 deletions

View file

@ -184,4 +184,46 @@ for (const v of vectors.ed_to_x25519) {
eq("cert-len", String(chain[0].length), "136");
}
// --- response framing guards (§6.1) -------------------------------------------
// A Session over a scripted server. The cipher states are the identity, so the
// bytes handed in are exactly what call() parses; readNoise wants at least 16,
// and trailing bytes past the frame length are ignored by design.
function scriptedSession(frame) {
const message = crypto.concat(frame, new Uint8Array(Math.max(0, 16 - frame.length)));
const wire = {
queue: crypto.concat(proto.u16BE(message.length), message),
pos: 0,
async readExact(n) {
if (this.pos + n > this.queue.length) throw new Error("script exhausted");
this.pos += n;
return this.queue.slice(this.pos - n, this.pos);
},
};
const passthrough = { encrypt: bytes => bytes, decrypt: bytes => bytes };
return new proto.Session(wire, { send() {}, close() {} }, passthrough, passthrough);
}
async function refuses(name, frame, op) {
let threw = false;
try { await scriptedSession(frame).call(op); } catch { threw = true; }
ok(name, threw, "call resolved instead of throwing");
}
// The shortest legal response is a type byte and a status byte.
await refuses("frame-too-short",
crypto.concat(proto.u32BE(1), Uint8Array.of(proto.OP.FETCH)), proto.OP.FETCH);
// A response reuses the request's type byte; a mismatch means the session
// desynchronised, which must not be read as a status.
await refuses("frame-op-mismatch",
crypto.concat(proto.u32BE(2), Uint8Array.of(proto.OP.RESOLVE, 0)), proto.OP.FETCH);
// The same frame with the right echo still passes, so the guard is not
// simply rejecting everything.
{
const session = scriptedSession(crypto.concat(proto.u32BE(2), Uint8Array.of(proto.OP.FETCH, 0)));
eq("frame-op-echo-ok", (await session.call(proto.OP.FETCH)).status, 0);
}
console.log(`${passed} checks passed`);