// #1873 T1 follow-up (org-defscan): bus start runs the live credential store's held-secret check on // every organization definition, and the launch check asks the bus the same question. A hit names // the definition and the key, never the text there or the credential's name. import test from 'node:test'; import assert from 'node:assert/strict'; import { readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { BusError } from '../src/broker.mjs'; import { REFUSALS } from '../src/refusals.mjs'; import { DEFINITION_HOLDS_CREDENTIAL, DEFINITION_SCAN_UNREADABLE, heldPath, scanDefinitions, scanOrganizations } from '../src/definition-scan.mjs'; import { startBroker } from '../src/runtime.mjs'; import { organizationWriter } from '../../organization/src/organization.mjs'; import { bootConfig, loadSystem } from '../../cli/src/config.mjs'; import { fixture, tmp } from '../../cli/tests/helpers.mjs'; import { entryFromTemplate, readTemplate } from '../../organization/src/definitions.mjs'; import { REPO_ROLES, writeJson } from '../../organization/tests/helpers.mjs'; // The fixture bus holds SENTINEL as coder's gitea token. const SENTINEL = 'sk-test-NOT-A-SECRET-1234'; const UNHELD = 'sk-test-UNHELD-LOOKS-LIKE-A-TOKEN-0000'; function files(dir) { return readdirSync(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? files(join(dir, e.name)) : e.isFile() ? [join(dir, e.name)] : []); } // An organization whose coder names org:builder, a copy of the coder template; `edit` changes the // builder entry before the file is written. `held` is the coder's gitea token at start. function org(t, edit = (entry) => entry, { held = SENTINEL } = {}) { const root = tmp(t, 'defscan-'); const builder = edit(entryFromTemplate(readTemplate(REPO_ROLES, 'coder'))); const f = fixture(root, 'acme', (doc) => ({ ...doc, definitions: { builder }, roles: { ...doc.roles, coder: { ...doc.roles.coder, definition: 'org:builder' } } })); writeFileSync(f.doc.roles.coder.credentials.gitea.file, held); const file = join(root, 'config/businesses/acme.json'); const boot = () => bootConfig({ system: loadSystem({ env: f.env }), businessId: 'acme', env: f.env }); const start = (b = boot()) => startBroker({ dataRoot: f.dataRoot, businesses: b.businesses, organization: b.organization }); return { root, f, file, boot, start }; } function refusedWithout(e, reason) { assert.ok(e instanceof BusError, String(e)); assert.equal(e.code, DEFINITION_HOLDS_CREDENTIAL); assert.equal(e.reason, reason); for (const text of [e.message, e.reason, String(e.stack), JSON.stringify(e)]) { assert.ok(!text.includes(SENTINEL), 'the refusal never carries the value'); assert.ok(!/gitea/.test(text), 'the refusal never names the credential'); } return true; } // The start's outcome: the refusal, or "started" after closing a bus that should not have started. const outcome = (start) => start.then(async (bus) => { await bus.close(); return 'started'; }, (e) => e); function nothingWritten(root) { for (const path of files(root)) { if (path.endsWith('.token') || path.endsWith('acme.json')) continue; assert.ok(!readFileSync(path).includes(SENTINEL), path); } } test('org-defscan: the refusal code is in the TI refusal table with a next step', () => { const entry = REFUSALS[DEFINITION_HOLDS_CREDENTIAL]; assert.ok(entry, 'definition-holds-credential is a known refusal'); assert.match(entry.rail, /^R\d+:/); assert.match(entry.nextStep, /definition/); assert.match(entry.nextStep, /issue the credential again/); assert.match(entry.nextStep, /For a hire, deny it/); // The issue-time check's fail-closed code for an organization file it cannot read. const unreadable = REFUSALS[DEFINITION_SCAN_UNREADABLE]; assert.ok(unreadable, 'definition-scan-unreadable is a known refusal'); assert.match(unreadable.rail, /^R\d+/); assert.match(unreadable.nextStep, /organization file/); assert.equal(new BusError(DEFINITION_SCAN_UNREADABLE).nextStep, unreadable.nextStep); }); test('org-defscan: a held credential in a definition\'s instructions refuses bus start, naming definition and key only', async (t) => { const o = org(t, (entry) => ({ ...entry, instructions: `${entry.instructions}\nThe forge token is ${SENTINEL}.\n` })); refusedWithout(await outcome(o.start()), 'definitions.builder instructions holds a credential this bus holds'); nothingWritten(o.root); }); test('org-defscan: a held credential in a personality field refuses bus start the same way', async (t) => { const o = org(t, (entry) => ({ ...entry, personality: { summary: `Careful. Key ${SENTINEL}.`, traits: ['plain'] } })); refusedWithout(await outcome(o.start()), 'definitions.builder personality.summary holds a credential this bus holds'); const p = org(t, (entry) => ({ ...entry, personality: { summary: 'Careful.', traits: ['plain', `x${SENTINEL}`] } })); refusedWithout(await outcome(p.start()), 'definitions.builder personality.traits[1] holds a credential this bus holds'); nothingWritten(o.root); nothingWritten(p.root); }); test('org-defscan: a definition in the file but not in the boot copy is checked too', async (t) => { const o = org(t); const boot = o.boot(); // Written after the load and before the bus starts: no instance names it, so only the raw read sees it. const doc = JSON.parse(readFileSync(o.file, 'utf8')); doc.definitions.spare = { ...doc.definitions.builder, instructions: `Spare: ${SENTINEL}\n` }; writeJson(o.file, doc); refusedWithout(await outcome(o.start(boot)), 'definitions.spare instructions holds a credential this bus holds'); }); test('org-defscan: a clean file starts, and token-looking text the store does not hold starts', async (t) => { for (const edit of [(entry) => entry, (entry) => ({ ...entry, instructions: `${entry.instructions}\nExample: ${UNHELD}\n`, personality: { summary: `Says ${UNHELD}.` } })]) { const o = org(t, edit); const bus = await o.start(); try { // The launch check's question: a clean entry answers null, a held one names the key only. const entry = o.boot().businesses.acme.definitions['org:builder']; assert.deepEqual(bus.definitionScan({ business: 'acme', definition: 'org:builder', entry }), { held: null }); const hit = bus.definitionScan({ business: 'acme', definition: 'org:builder', entry: { ...entry, instructions: `x ${SENTINEL}` } }); assert.deepEqual(hit, { held: 'instructions' }); // A malformed question refuses rather than answering clean. for (const bad of [{}, { business: 'acme', definition: 'builder', entry }, { business: 'other', definition: 'org:builder', entry }, { business: 'acme', definition: 'org:builder', entry: null }, { business: 'acme', definition: 'org:builder', entry: [entry] }]) assert.throws(() => bus.definitionScan(bad), (e) => e.code === 'invalid-request'); } finally { await bus.close(); } } }); // The coder's gitea credential as the human verb issues or rotates it. function verbs(o, bus) { const human = bus.broker.bindHuman({ business: 'acme', human: o.f.doc.human, via: 'cli', outsideAgent: true }); return { issue: (token) => bus.broker.request(human, { verb: 'credentials.issue', args: { agent: 'coder', service: 'gitea', token, date: '2099-01-01' } }), revoke: () => bus.broker.request(human, { verb: 'credentials.revoke', args: { agent: 'coder', service: 'gitea', reason: 'test' } }), }; } // Whether the bus holds `value` now, asked the way the launch check asks. const holds = (bus, value) => bus.definitionScan({ business: 'acme', definition: 'org:builder', entry: { instructions: `x ${value}` } }).held !== null; const state = (bus) => bus.credentials.status().find((e) => e.instance === 'acme/coder' && e.service === 'gitea')?.state; function issueRefused(e, value, reason) { assert.ok(e instanceof BusError, String(e)); assert.equal(e.code, DEFINITION_HOLDS_CREDENTIAL); assert.equal(e.reason, reason); for (const text of [e.message, e.reason, String(e.stack), JSON.stringify(e)]) { assert.ok(!text.includes(value), 'the refusal never carries the value'); assert.ok(!/gitea/.test(text), 'the refusal never names the credential'); } return true; } // No file under the root holds `value` except the organization file it was written into and that // file's own history copies. function onlyInOrganization(root, value) { for (const path of files(root)) if (!path.endsWith('acme.json') && !path.includes('/businesses/.history/acme/')) assert.ok(!readFileSync(path).includes(value), path); } test('org-defscan: a credential issued between the final launch admission and spawn is refused at issue; the store stays as it was', async (t) => { // The definition carries text that is not held at start. After the launch's final admission answers // ready, the human rotates the coder's token to that text with the same date. On the bus before this // fix the rotate succeeded and the agent spawned with a held value in its contract. const FIRST = 'fake_first_token_0123456789'; const o = org(t, (entry) => ({ ...entry, instructions: `${entry.instructions}\nExample ${SENTINEL}\n` }), { held: 'fake_boot_token_5555555555' }); const boot = o.boot(); const bus = await o.start(boot); t.after(() => bus.close()); const v = verbs(o, bus); // A clean issue still works. assert.equal(v.issue(FIRST).state, 'valid'); const writer = organizationWriter({ dir: boot.organization.dir, rolesDir: boot.organization.rolesDir, dataRoot: o.f.dataRoot }); const revision = writer.read('acme').revision; const changes = files(o.root).filter((p) => p.endsWith('changes.jsonl')).map((p) => [p, readFileSync(p, 'utf8')]); const entry = o.boot().businesses.acme.definitions['org:builder']; assert.deepEqual(bus.definitionScan({ business: 'acme', definition: 'org:builder', entry }), { held: null }); const final = () => bus.admission({ business: 'acme', projects: [], instance: 'coder', revision }); assert.equal(final().ready, true); // Between the admission's answer and the spawn: the rotate. assert.throws(() => v.issue(SENTINEL), (e) => issueRefused(e, SENTINEL, 'organization acme definitions.builder instructions holds the credential being issued')); // Nothing changed: the old token is held, the new one is not, the file and its receipts are as they were. assert.ok(holds(bus, FIRST) && !holds(bus, SENTINEL)); assert.equal(state(bus), 'valid'); assert.equal(writer.read('acme').revision, revision); for (const [p, text] of changes) assert.equal(readFileSync(p, 'utf8'), text, p); onlyInOrganization(o.root, SENTINEL); assert.equal(final().ready, true, 'the contract the agent gets holds no held value'); }); test('org-defscan: issue over a revoked entry refuses the same way for a personality field, and a clean issue then works', async (t) => { const o = org(t, (entry) => ({ ...entry, personality: { summary: 'Careful.', traits: ['plain', `uses ${SENTINEL}`] } }), { held: 'fake_boot_token_5555555555' }); const bus = await o.start(); t.after(() => bus.close()); const v = verbs(o, bus); v.revoke(); assert.equal(state(bus), 'revoked'); assert.throws(() => v.issue(SENTINEL), (e) => issueRefused(e, SENTINEL, 'organization acme definitions.builder personality.traits[1] holds the credential being issued')); assert.equal(state(bus), 'revoked'); assert.ok(!holds(bus, SENTINEL)); onlyInOrganization(o.root, SENTINEL); const issued = v.issue('fake_clean_token_4444444444'); assert.deepEqual([issued.verb, issued.state], ['issue', 'valid']); }); test('org-defscan: a value held without the issue verb still refuses the launch at its final admission, even when the file revision is unchanged', async (t) => { // Defence behind the issue check: the store comes to hold text already in the definition by a path // other than the human verb, which leaves the organization file's bytes, so its revision, as they were. const ROTATED = 'fake_rotated_token_9876543210'; const o = org(t, (entry) => ({ ...entry, instructions: `${entry.instructions}\nExample ${ROTATED}\n` })); const boot = o.boot(); const bus = await o.start(boot); t.after(() => bus.close()); const writer = organizationWriter({ dir: boot.organization.dir, rolesDir: boot.organization.rolesDir, dataRoot: o.f.dataRoot }); const revision = writer.read('acme').revision; const final = () => bus.admission({ business: 'acme', projects: [], instance: 'coder', revision }); assert.equal(final().ready, true); bus.credentials.issue('acme/coder', 'gitea', ROTATED, '2099-01-01'); assert.equal(writer.read('acme').revision, revision, 'the store change left the file as it was'); assert.deepEqual(final(), { ready: false, revision: null, code: DEFINITION_HOLDS_CREDENTIAL }); // An instance on a shipped template is not asked about a definition. assert.equal(bus.admission({ business: 'acme', projects: [], instance: 'reviewer', revision }).ready, true); }); test('org-defscan: issue checks the other organizations the bus serves, and refuses when one\'s file cannot be read', async (t) => { // The bus serves acme and beta. beta's file has a definition holding text acme's coder then tries to // be issued; later beta's file stops being JSON. Neither issue changes acme's store or file. const o = org(t, (entry) => entry, { held: 'fake_boot_token_5555555555' }); const boot = o.boot(); const betaFile = join(o.root, 'config/businesses/beta.json'); const acme = JSON.parse(readFileSync(o.file, 'utf8')); const spare = { ...acme.definitions.builder, personality: { summary: 'Spare.', voice: `quotes ${SENTINEL}` } }; writeJson(betaFile, { ...acme, id: 'beta', definitions: { spare } }); const bus = await o.start({ ...boot, businesses: { ...boot.businesses, beta: { ...boot.businesses.acme, id: 'beta' } } }); t.after(() => bus.close()); const v = verbs(o, bus); const writer = organizationWriter({ dir: boot.organization.dir, rolesDir: boot.organization.rolesDir, dataRoot: o.f.dataRoot }); const revision = writer.read('acme').revision; assert.throws(() => v.issue(SENTINEL), (e) => issueRefused(e, SENTINEL, 'organization beta definitions.spare personality.voice holds the credential being issued')); writeFileSync(betaFile, '{ not json'); const CLEAN = 'fake_clean_token_4444444444'; assert.throws(() => v.issue(CLEAN), (e) => e instanceof BusError && e.code === DEFINITION_SCAN_UNREADABLE && !JSON.stringify(e).includes(CLEAN) && !String(e.stack).includes(CLEAN)); for (const value of [SENTINEL, CLEAN]) assert.ok(!holds(bus, value)); assert.equal(state(bus), 'valid'); assert.equal(writer.read('acme').revision, revision); onlyInOrganization(o.root, CLEAN); // With beta's file readable again, the clean issue goes through. writeJson(betaFile, { ...acme, id: 'beta', definitions: {} }); assert.equal(v.issue(CLEAN).state, 'valid'); assert.ok(holds(bus, CLEAN)); }); test('org-defscan: the issue check covers every organization the bus serves, and an unreadable file refuses', () => { const held = 'HELD-VALUE-77'; const assertClean = (value) => { if ((JSON.stringify(value) ?? '').includes(held)) throw new BusError('credential-leak'); }; const own = { id: 'acme', document: { definitions: { builder: { instructions: 'fine' } } } }; const files = { beta: { readable: true, document: { definitions: { spare: { instructions: 'fine', personality: { voice: `say ${held}` } } } } }, gamma: { readable: true, document: {} }, broken: { readable: false } }; const reads = []; const readRaw = (id) => { reads.push(id); if (id === 'throws') throw new Error('EACCES'); return files[id]; }; assert.doesNotThrow(() => scanOrganizations({ organizations: ['acme', 'gamma'], own, readRaw }, assertClean)); assert.deepEqual(reads, ['gamma'], 'the issuing organization is the document already read'); assert.throws(() => scanOrganizations({ organizations: ['acme', 'gamma', 'beta'], own, readRaw }, assertClean), (e) => e.code === DEFINITION_HOLDS_CREDENTIAL && e.reason === 'organization beta definitions.spare personality.voice holds the credential being issued' && !JSON.stringify(e).includes(held)); assert.throws(() => scanOrganizations({ organizations: ['acme', { ...own, id: 'x' }.id], own: { id: 'acme', document: { definitions: { [held]: {} } } }, readRaw: () => ({ readable: true, document: {} }) }, assertClean), (e) => e.reason === 'organization acme definitions.* key holds the credential being issued' && !e.reason.includes(held)); for (const bad of ['broken', 'throws', 'missing']) assert.throws(() => scanOrganizations({ organizations: ['acme', bad], own, readRaw }, assertClean), (e) => e.code === DEFINITION_SCAN_UNREADABLE && e.reason === undefined); }); test('org-defscan: heldPath names the first place, a key by its parent, and never the text', () => { const held = 'HELD-VALUE-77'; const assertClean = (value) => { if ((JSON.stringify(value) ?? '').includes(held)) throw new BusError('credential-leak'); }; assert.equal(heldPath({ instructions: 'fine', personality: { traits: ['a', 'b'] } }, assertClean), null); assert.equal(heldPath({ instructions: 'fine', personality: { traits: ['a', `b${held}`] } }, assertClean), 'personality.traits[1]'); assert.equal(heldPath({ role: { tools: [held] } }, assertClean), 'role.tools[0]'); assert.equal(heldPath({ personality: { [held]: 'x' } }, assertClean), 'personality key'); assert.equal(heldPath({ [`${held}`]: 'x' }, assertClean), 'key'); assert.throws(() => scanDefinitions({ 'org:a': { instructions: 'ok' }, 'org:b': { instructions: held } }, assertClean, { prefix: 'org:' }), (e) => e.code === DEFINITION_HOLDS_CREDENTIAL && e.reason === 'definitions.b instructions holds a credential this bus holds'); // A name outside the loader's pattern is not quoted. assert.throws(() => scanDefinitions({ 'Bad Name': { instructions: held } }, assertClean), (e) => e.reason === 'definitions.* instructions holds a credential this bus holds'); assert.throws(() => scanDefinitions({ [held]: { instructions: 'ok' } }, assertClean), (e) => e.reason === 'definitions.* key holds a credential this bus holds' && !e.reason.includes(held)); assert.doesNotThrow(() => scanDefinitions(undefined, assertClean)); }); test('org-defscan: an approved hire whose definition holds a credential issued after the raise refuses before the decision is consumed', async (t) => { // The chief raises a hire whose instructions carry text the store does not hold yet. The proposal is // private, so the issue-time check cannot see it, and the store then comes to hold that text. The // approval runs the same definition check as start, launch and issue: the refusal names the code, the // decision stays open, and the organization file is as it was. const LATER = 'fake_later_token_0123456789'; const o = org(t, (entry) => entry, { held: 'fake_boot_token_5555555555' }); const boot = o.boot(); const bus = await o.start(boot); t.after(() => bus.close()); const chief = bus.broker.bindLaunch({ business: 'acme', role: 'chief', run: 'chief-run', harness: 'pi' }); bus.broker.request(chief, { verb: 'role.claim' }); const inline = { tools: ['read'], network: 'none', authority: { withinRole: [], crossRole: [] }, credentials: [], instructions: `Read and report.\nExample ${LATER}\n` }; const raised = await bus.broker.request(chief, { verb: 'hire.raise', args: { op: 'hire', level: 'org', name: 'scout', role: { inline }, route: { harness: 'pi', model: 'glm-5.3-flash' }, reason: 'Need help' } }); // Issued through the store, so the organization file and its revision stay as they were and the // approval reaches the definition check rather than the revision check. bus.credentials.issue('acme/coder', 'gitea', LATER, '2099-01-01'); assert.ok(holds(bus, LATER)); const writer = organizationWriter({ dir: boot.organization.dir, rolesDir: boot.organization.rolesDir, dataRoot: o.f.dataRoot }); const before = readFileSync(o.file); const revision = writer.read('acme').revision; const owner = bus.broker.bindConsole({ business: 'acme', human: o.f.doc.human, subject: { provider: 'test', iss: 'https://issuer.test', sub: 'owner' } }); const refused = (e) => { assert.equal(e.code, DEFINITION_HOLDS_CREDENTIAL); for (const text of [e.message, String(e.reason), String(e.stack), JSON.stringify(e)]) assert.ok(!text.includes(LATER), 'the refusal never carries the value'); return true; }; await assert.rejects(bus.broker.request(owner, { verb: 'hire.resolve.console', args: { id: raised.id, choice: 'yes', digest: raised.hire.digest } }), refused); // Nothing was written. assert.deepEqual(readFileSync(o.file), before); assert.equal(writer.read('acme').revision, revision); assert.ok(!Object.hasOwn(writer.read('acme').document.roles, 'scout')); // The decision was not consumed: a second approval meets the same check, not decision-closed. await assert.rejects(bus.broker.request(owner, { verb: 'hire.resolve.console', args: { id: raised.id, choice: 'yes', digest: raised.hire.digest } }), refused); assert.deepEqual(readFileSync(o.file), before); // A hire with clean text, raised and approved on the same bus, is written as before. const clean = await bus.broker.request(chief, { verb: 'hire.raise', args: { op: 'hire', level: 'org', name: 'helper', role: { inline: { ...inline, instructions: 'Read and report.\n' } }, route: { harness: 'pi', model: 'glm-5.3-flash' }, reason: 'Need help' } }); assert.equal((await bus.broker.request(owner, { verb: 'hire.resolve.console', args: { id: clean.id, choice: 'yes', digest: clean.hire.digest } })).choice, 'yes'); assert.ok(Object.hasOwn(writer.read('acme').document.roles, 'helper')); onlyInOrganization(join(o.root, 'config/businesses'), LATER); }); test('org-defscan: the boot copy snapshot\'s definitions (the root agents file\'s) are checked at start and at issue', async (t) => { // A4: a root definition reaches an organization through the boot copy snapshot, which every reload // re-applies. Its text is in no organization file, so the start and issue checks read the snapshot. const LATER = 'fake_later_token_0123456789'; const snapshot = (instructions) => ({ org: 'acme', root: 'ok', rootAgents: {}, copies: {}, definitions: { rooted: { ...entryFromTemplate(readTemplate(REPO_ROLES, 'coder')), instructions } } }); const startWith = (o, copies, b = o.boot()) => startBroker({ dataRoot: o.f.dataRoot, businesses: b.businesses, organization: b.organization, copies }); // Held at start: the start refuses, naming the snapshot's definition and key. const held = org(t); refusedWithout(await outcome(startWith(held, snapshot(`Root copy ${SENTINEL}\n`))), 'boot copy definitions.rooted instructions holds a credential this bus holds'); // Not held at start: the bus starts, then issuing that text refuses and the store is as it was. const o = org(t, (entry) => entry, { held: 'fake_boot_token_5555555555' }); const bus = await startWith(o, snapshot(`Root copy ${LATER}\n`)); t.after(() => bus.close()); assert.throws(() => verbs(o, bus).issue(LATER), (e) => issueRefused(e, LATER, 'organization acme boot copy definitions.rooted instructions holds the credential being issued')); assert.ok(!holds(bus, LATER)); assert.equal(state(bus), 'valid'); assert.equal(verbs(o, bus).issue('fake_clean_token_4444444444').state, 'valid'); });