Files
stack/packages/webui/tests/proof.test.mjs
T
jason.woltje 7247d80c7a row 196: human boundary on peer credentials, interpreter receipt and the Console tester socket (#1694)
Candidate 527b9a1e6 (gander/row196-r6-prd19, base f48725cfc), the 39 manifest paths of CANDIDATE.sha256 537d30aef70b6cd1 at ec1e7fad8. Duckworth approved round 1 outside B1/B2 (rev 2973, comment 30145) and round 2 merge-scoped (rev 2986, comment 30156). Gate gander-0066 on grant sage-2145: 2334 pass, 0 fail, 1 skip; mutants 38/38 killed. BUILD-LOG lines and Duckworth's C1 DEFERRED entry follow as a doc-only append.
2026-10-21 15:55:39 -05:00

87 lines
6.1 KiB
JavaScript

// Row 178: fixture proof values only. No installed proof, proxy or unit is read.
import test from 'node:test';
import assert from 'node:assert/strict';
import { createServer, request } from 'node:http';
import { mkdtempSync, writeFileSync, chmodSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import * as serve from '../src/serve.mjs';
import { close } from './fixture.mjs';
import { startTesterSocket } from '../src/tester-socket.mjs';
import { testerRequest } from '../../cli/src/tester-http.mjs';
const HOST = 'console.example', HEADER = 'x-console-proof', VALUE = 'fixture_proxy_proof_0123456789abcdef_0123456789';
const preview = { host: HOST, header: 'x-user', user: 'jason' };
function ask(port, { host = HOST, headers = {}, path = '/api/bus/tasks', method = 'GET', body } = {}) {
return new Promise((resolve, reject) => {
const q = request({ host: '127.0.0.1', port, path, method, headers: Object.fromEntries(Object.entries({ host, 'x-user': 'jason', ...headers }).filter(([, value]) => value !== undefined)) }, r => {
let text = ''; r.on('data', c => text += c); r.on('end', () => resolve({ status: r.statusCode, text, body: JSON.parse(text) }));
}); q.on('error', reject); q.end(body);
});
}
async function setup(t) {
const root = mkdtempSync(join(tmpdir(), 'proof-http-')), file = join(root, 'fixture.token');
t.after(() => rmSync(root, { recursive: true, force: true }));
writeFileSync(file, VALUE + '\n', { mode: 0o600 });
const proof = serve.proofOptions({ proofHeader: HEADER, proofFile: file }, preview);
const calls = [], logs = [];
const board = createServer((q, r) => { calls.push('board'); r.setHeader('content-type', 'application/json'); r.end('{"sessions":[]}'); });
await new Promise(r => board.listen(0, '127.0.0.1', r)); t.after(() => close(board));
const server = await serve.startServer({ port: 0, preview, proof, board: `http://127.0.0.1:${board.address().port}`,
bus: { tasks: async () => { calls.push('tasks'); return []; } }, send: async () => { calls.push('send'); return { id: 'receipt-1' }; }, log: line => logs.push(line) });
t.after(() => close(server));
return { port: server.address().port, calls, logs, proof, file };
}
test('proxy proof admits named reads and sends, reads its fixture once, and never exposes the value', async t => {
const f = await setup(t), headers = { [HEADER]: VALUE };
writeFileSync(f.file, 'changed_fixture_value_012345678901234567890');
assert.equal((await ask(f.port, { headers })).status, 200);
assert.equal((await ask(f.port, { headers, path: '/api/board' })).status, 200);
const sent = await ask(f.port, { headers: { ...headers, origin: `https://${HOST}`, 'sec-fetch-site': 'same-origin', 'content-type': 'application/json' }, method: 'POST', path: '/api/bus/message', body: JSON.stringify({ to: 'coder', body: 'fixture' }) });
assert.equal(sent.status, 200); assert.equal(sent.body.id, 'receipt-1');
assert.deepEqual(f.calls, ['tasks', 'board', 'send']);
const config = await ask(f.port, { headers, path: '/api/config' });
assert.equal(config.status, 200); assert.ok(!config.text.includes(VALUE));
assert.ok(!f.logs.join('\n').includes(VALUE));
});
test('proxy proof refuses missing wrong short or repeated proof before auth and before any read', async t => {
const f = await setup(t);
for (const headers of [{}, { [HEADER]: 'x'.repeat(VALUE.length) }, { [HEADER]: VALUE.slice(1) }, { [HEADER]: [VALUE, VALUE] }]) {
for (const path of ['/api/bus/tasks', '/api/board']) {
const result = await ask(f.port, { headers, path });
assert.equal(result.status, 401); assert.deepEqual(result.body, { error: 'proxy proof required' });
assert.ok(!result.text.includes(VALUE));
}
}
// Writes are fenced by the same proof check, before the human send callback.
for (const headers of [{}, { [HEADER]: 'x'.repeat(VALUE.length) }, { [HEADER]: VALUE.slice(1) }, { [HEADER]: [VALUE, VALUE] }]) {
const result = await ask(f.port, { headers: { ...headers, origin: `https://${HOST}`, 'sec-fetch-site': 'same-origin', 'content-type': 'application/json' }, path: '/api/bus/message', method: 'POST', body: JSON.stringify({ to: 'coder', body: 'fixture' }) });
assert.equal(result.status, 401); assert.deepEqual(result.body, { error: 'proxy proof required' });
assert.ok(!result.text.includes(VALUE));
}
const noIdentity = await ask(f.port, { headers: { 'x-user': 'wrong' } });
assert.equal(noIdentity.status, 401); assert.deepEqual(noIdentity.body, { error: 'proxy proof required' });
assert.deepEqual(f.calls, []); assert.ok(!f.logs.join('\n').includes(VALUE));
});
test('proxy proof header is refused on other Hosts and on the tester listener', async t => {
const f = await setup(t);
for (const host of [`127.0.0.1:${f.port}`, 'other.example']) {
assert.equal((await ask(f.port, { host, headers: { 'x-user': undefined, [HEADER]: VALUE } })).status, 403);
}
assert.equal((await ask(f.port, { host: `127.0.0.1:${f.port}`, headers: { 'x-user': undefined, [HEADER]: '' } })).status, 403);
const testerRoot = mkdtempSync(join(tmpdir(),'tester-proof-'));
t.after(()=>rmSync(testerRoot,{recursive:true,force:true}));
const server = await startTesterSocket({ dataRoot: testerRoot, testerProofHeader: HEADER, board: 'http://127.0.0.1:9' });
t.after(() => close(server));
const result = await testerRequest({dataRoot:testerRoot,headers:{[HEADER]:VALUE},path:'/api/config'});
assert.equal(result.status, 403); assert.deepEqual(f.calls, []);
});
test('proof options reject unsafe or colliding header names without disclosing file contents', t => {
const f = mkdtempSync(join(tmpdir(), 'proof-options-')); t.after(() => rmSync(f, { recursive: true, force: true }));
const file = join(f, 'fixture.token'); writeFileSync(file, VALUE, { mode: 0o600 });
for (const proofHeader of ['host', 'origin', 'x-user', 'x-mosaic-tester', 'bad header', '']) {
assert.throws(() => serve.proofOptions({ proofHeader, proofFile: file }, preview), /proof/);
}
chmodSync(file, 0o400);
assert.equal(serve.proofOptions({ proofHeader: HEADER, proofFile: file }, preview).header, HEADER);
});