Files
stack/packages/webui/tests/proof-cli.test.mjs
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

88 lines
6.6 KiB
JavaScript

import { leasePort } from '../../../scripts/testing/lane-fixtures.mjs';
import { after as laneAfter } from 'node:test';
import { testerRequest } from '../../cli/src/tester-http.mjs';
import test from 'node:test';
import assert from 'node:assert/strict';
import { spawn, spawnSync } from 'node:child_process';
import { once } from 'node:events';
import { createServer } from 'node:net';
import { request } from 'node:http';
import { testerFixture } from './tester-fixture.mjs';
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, chmodSync, symlinkSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const CLI = new URL('../src/cli.mjs', import.meta.url).pathname;
const VALUE = 'fixture_proxy_proof_0123456789abcdef_0123456789';
const PREVIEW = ['--public-host', 'console.example', '--auth-header', 'x-user', '--allow-user', 'jason'];
function fixture(t) {
const root = mkdtempSync(join(tmpdir(), 'proof-cli-')); t.after(() => rmSync(root, { recursive: true, force: true }));
const file = join(root, 'fixture.token'); writeFileSync(file, VALUE + '\n', { mode: 0o600 });
return { root, file, env: { ...process.env, MOSAIC_CONFIG: join(root, 'missing-config.json') } };
}
test('proof startup refuses partial flags non-preview and invalid fixture file metadata or bytes', t => {
const f = fixture(t), proof = ['--proof-header', 'x-console-proof', '--proof-file', f.file];
const sets = [[...PREVIEW, '--proof-header', 'x-console-proof'], [...PREVIEW, '--proof-file', f.file], proof];
const missing = join(f.root, 'missing'), dir = join(f.root, 'directory'), link = join(f.root, 'link');
mkdirSync(dir); symlinkSync(f.file, link);
for (const file of [missing, dir, link]) sets.push([...PREVIEW, '--proof-header', 'x-console-proof', '--proof-file', file]);
const cases = [
...sets.map(flags => ({ flags })),
...[['mode', VALUE, 0o644], ['short', 'short_fixture', 0o600], ['newline', VALUE.slice(0, 20) + '\n' + VALUE.slice(20), 0o600], ['bad', VALUE + '!', 0o600], ['two-final-lines', VALUE + '\n\n', 0o600]].map(([name, value, mode]) => {
const file = join(f.root, name); writeFileSync(file, value, { mode }); chmodSync(file, mode); // umask-proof (row 243)
return { flags: [...PREVIEW, '--proof-header', 'x-console-proof', '--proof-file', file], value };
}),
];
// Simulate another uid via the fixture child's fstat; never chown a host file.
const hook = join(f.root, 'other-owner.mjs');
writeFileSync(hook, `import fs from 'node:fs';import {syncBuiltinESMExports} from 'node:module';const original=fs.fstatSync;fs.fstatSync=(...args)=>{const s=original(...args);s.uid=process.getuid()+1;return s;};syncBuiltinESMExports();`);
cases.push({ flags: [...PREVIEW, ...proof], env: { ...f.env, NODE_OPTIONS: `--import=${hook}` } });
for (const c of cases) {
const r = spawnSync(process.execPath, [CLI, 'serve', '--port', '0', '--board', 'http://127.0.0.1:9', ...c.flags], { env: c.env ?? f.env, encoding: 'utf8', timeout: 5000 });
assert.equal(r.status, 2, 'invalid proof startup must refuse'); assert.match(r.stderr, /proof/i);
assert.ok(!r.stderr.includes(VALUE)); assert.ok(!r.stdout.includes(VALUE));
if (c.value) assert.ok(!r.stderr.includes(c.value));
}
});
test('serve argv carries only fixture proof path and header; startup explicitly reports proof on or off', async t => {
const f = fixture(t);
for (const on of [true, false]) {
const child = spawn(process.execPath, [CLI, 'serve', '--port', '0', '--board', 'http://127.0.0.1:9', ...PREVIEW, ...(on ? ['--proof-header', 'x-console-proof', '--proof-file', f.file] : [])], { env: f.env, stdio: ['ignore', 'pipe', 'pipe'] });
let out = '', err = ''; child.stdout.on('data', c => out += c); child.stderr.on('data', c => err += c);
t.after(async () => { if (child.exitCode === null && child.signalCode === null) { child.kill('SIGTERM'); await once(child, 'close'); } });
await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(Error('fixture CLI startup timeout')), 5000);
const check = () => { if (out.includes('Ctrl-C')) { clearTimeout(timer); resolve(); } };
child.stdout.on('data', check); child.once('exit', () => { clearTimeout(timer); reject(Error('fixture CLI exited before startup')); }); check();
});
assert.match(out, new RegExp(`proxy proof ${on ? 'on' : 'off'}`));
const cmdline = readFileSync(`/proc/${child.pid}/cmdline`, 'utf8');
assert.ok(!cmdline.includes(VALUE)); assert.ok(!out.includes(VALUE)); assert.ok(!err.includes(VALUE));
if (on) { assert.ok(cmdline.includes(f.file)); assert.ok(cmdline.includes('--proof-header')); }
child.kill('SIGTERM'); await once(child, 'close');
}
});
test('CLI applies proof on the main listener and rejects that header on its tester listener', async t => {
const f = await testerFixture(t), file = join(f.root, 'proof.fixture');
writeFileSync(file, VALUE, { mode: 0o600 });
await new Promise(r=>f.web.close(r));
const child = spawn(process.execPath, [CLI, 'serve', '--port', '0', '--board', 'http://127.0.0.1:9', '--tester-socket', '--business', 'acme', ...PREVIEW, '--proof-header', 'x-console-proof', '--proof-file', file], { env: f.f.env, stdio: ['ignore', 'pipe', 'pipe'] });
let output = ''; child.stdout.on('data', c => output += c); child.stderr.resume();
t.after(async () => { if (child.exitCode === null && child.signalCode === null) { child.kill('SIGTERM'); await once(child, 'close'); } });
await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(Error('fixture tester startup timeout')), 5000);
const check = () => { if (output.includes('Ctrl-C')) { clearTimeout(timer); resolve(); } };
child.stdout.on('data', check); child.once('exit', () => { clearTimeout(timer); reject(Error('fixture tester startup refused')); }); check();
});
const port = Number(output.match(/Mosaic Console: http:\/\/127\.0\.0\.1:(\d+)/)[1]);
const get = (port, headers) => new Promise((resolve, reject) => {
const q = request({ host: '127.0.0.1', port, path: '/api/config', headers }, r => {
let text = ''; r.on('data', c => text += c); r.on('end', () => resolve({ status: r.statusCode, text }));
}); q.on('error', reject); q.end();
});
assert.equal((await get(port, { host: 'console.example', 'x-user': 'jason' })).status, 401);
const good = await get(port, { host: 'console.example', 'x-user': 'jason', 'x-console-proof': VALUE });
assert.equal(good.status, 200); assert.ok(!good.text.includes(VALUE));
assert.equal((await testerRequest({dataRoot:f.f.dataRoot,path:'/api/config',headers:{'x-console-proof':VALUE}})).status, 403);
});