56 lines
3.0 KiB
JavaScript
56 lines
3.0 KiB
JavaScript
// P4: broker security boundary. Mutants: granted user accepted; secret in error/log.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { Broker } from '../src/broker.mjs';
|
|
import { Store } from '../src/store.mjs';
|
|
|
|
const verbs = ['proxy.accounts', 'proxy.login.start', 'proxy.login.submit', 'proxy.login.status',
|
|
'proxy.login.cancel', 'proxy.apikey.add', 'proxy.priority.set', 'proxy.group.models.set', 'proxy.keys'];
|
|
function setup(t, proxyPage = async () => ({ ok: true })) {
|
|
const root = mkdtempSync(join(tmpdir(), 'p4-bus-'));
|
|
const store = new Store(root);
|
|
t.after(() => { store.close(); rmSync(root, { recursive: true, force: true }); });
|
|
const calls = [];
|
|
const broker = new Broker({ store, proxyPage: proxyPage && (async (...args) => { calls.push(args); return proxyPage(...args); }),
|
|
access: () => ({ accessVersion: 1, revision: 1, users: [], grants: [
|
|
{ id: 'g-1', user: 'alice', business: 'demo', projects: ['*'], instances: ['coder'], level: 'collaborator' },
|
|
] }),
|
|
businesses: { demo: { id: 'demo', human: 'owner', arbiters: { technical: 'coder', delivery: 'coder' },
|
|
vars: { 'bus.tester': true }, roles: { coder: { definition: 'coder', authority: { withinRole: ['message.send'], crossRole: [] } } } } },
|
|
});
|
|
const caps = {
|
|
human: broker.bindHuman({ business: 'demo', human: 'owner', via: 'cli', outsideAgent: true }),
|
|
tester: broker.bindTester({ business: 'demo' }),
|
|
agent: broker.bindLaunch({ business: 'demo', role: 'coder', run: 'p4-run', harness: 'pi' }),
|
|
user: broker.bindUser({ business: 'demo', user: 'alice' }),
|
|
};
|
|
return { calls, store, call: (who, verb, args = {}) => broker.request(caps[who], { verb, args }) };
|
|
}
|
|
for (const verb of verbs) {
|
|
test(`${verb}: owner calls page once with business/human scope`, async t => {
|
|
const s = setup(t);
|
|
assert.deepEqual(await s.call('human', verb), { ok: true });
|
|
assert.deepEqual(s.calls, [[verb, {}, { business: 'demo', human: 'owner' }]]);
|
|
});
|
|
for (const who of ['tester', 'agent', 'user']) test(`${verb}: ${who} human-required`, async t => {
|
|
const s = setup(t);
|
|
await assert.rejects(async () => s.call(who, verb), { code: 'human-required' });
|
|
assert.equal(s.calls.length, 0);
|
|
});
|
|
test(`${verb}: not-configured`, async t => {
|
|
const s = setup(t, null);
|
|
await assert.rejects(async () => s.call('human', verb), { code: 'not-configured' });
|
|
});
|
|
}
|
|
test('API-key canary never enters broker events or unknown error output', async t => {
|
|
const canary = 'p4-canary-key';
|
|
const s = setup(t, async () => { throw new Error(canary); });
|
|
await assert.rejects(async () => s.call('human', 'proxy.apikey.add', { provider: 'z.ai', key: canary }),
|
|
e => e.code === 'proxy-down' && !e.message.includes(canary));
|
|
assert.equal(s.calls.length, 1);
|
|
assert.equal(JSON.stringify(s.store.all('SELECT body FROM events')).includes(canary), false);
|
|
});
|