Candidate527b9a1e6(gander/row196-r6-prd19, basef48725cfc), the 39 manifest paths of CANDIDATE.sha256 537d30aef70b6cd1 atec1e7fad8. 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.
87 lines
6.1 KiB
JavaScript
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);
|
|
});
|