bus: row 199 mutants M06 and M26 now die; a test makes revoke's unlink fail and finds the token already gone from memory; the bus process refuses a boot config carrying publishTls (invalid-host-request) instead of dropping it, and the process test asserts the refusal (sage-1563, #1697)

This commit is contained in:
jason.woltje committed 2026-10-20 18:14:43 -05:00
1 parent 5fa7b25532
commit 116eeec48c
3 files changed
+33 -4

No files matched your search

+4 -2
View File
@@ -110,8 +110,10 @@ if (!process.send) {
booted = true; booted = true;
clearTimeout(timer); clearTimeout(timer);
// `trackers` is S3's plain-data boot config; the adapter is loaded only when a business has one. // `trackers` is S3's plain-data boot config; the adapter is loaded only when a business has one.
// Row 199: publishTls is the tests' scratch CA; no boot config sets it. // Row 199: publishTls is the tests' scratch CA, for an in-process runtime only. The host's boot
const { trackers, publishTls: _tls, ...config } = message.config ?? {}; // config never carries it, so a boot that does is refused rather than trusted.
const { trackers, ...config } = message.config ?? {};
if (Object.hasOwn(config, 'publishTls')) throw new BusError('invalid-host-request');
if (trackers) { if (trackers) {
const { tasksAdapter } = await import('../../tasks/src/adapter.mjs'); const { tasksAdapter } = await import('../../tasks/src/adapter.mjs');
config.tasks = tasksAdapter({ trackers }); config.tasks = tasksAdapter({ trackers });
@@ -134,6 +134,24 @@ test('T5: a failed commit leaves the token on disk but not in memory, and a bad
await assert.rejects(s.read(c), /credential-unavailable/); await assert.rejects(s.read(c), /credential-unavailable/);
}); });
test('T5: revoke drops the token from memory before the unlink, so a failed unlink still leaves it revoked', async (t) => {
const s = setup(t);
const c = s.open();
c.issue('acme/coder', 'gitea', TOKEN, '2099-01-01');
assert.deepEqual(await s.read(c), { same: true, rotated: false });
// An agent dir the bus can't write: the unlink fails with EACCES.
const agentDir = join(s.storeRoot, 'acme', 'coder');
chmodSync(agentDir, 0o500);
try {
assert.throws(() => c.revoke('acme/coder', 'gitea'), /credential-file/);
} finally {
chmodSync(agentDir, 0o700);
}
assert.equal(existsSync(s.file), true);
assert.equal(s.state(c), 'revoked');
await assert.rejects(s.read(c), /credential-unavailable/);
});
test('T5: revoke of a {file} reference forgets the token and never unlinks the file', async (t) => { test('T5: revoke of a {file} reference forgets the token and never unlinks the file', async (t) => {
const s = setup(t); const s = setup(t);
const path = join(s.root, 'outside-token'); const path = join(s.root, 'outside-token');
+11 -2
View File
@@ -247,7 +247,7 @@ test('runtime: withGitea keys from the open ticket only, and the store root is t
// Row 199 (D9b, deviation 11): the bus process answers the socket's five publish ops from their own // Row 199 (D9b, deviation 11): the bus process answers the socket's five publish ops from their own
// launchOp lines. A LAUNCH_OPS name without one would get launch-op-refused. A boot config that // launchOp lines. A LAUNCH_OPS name without one would get launch-op-refused. A boot config that
// carries publishTls still boots; process.mjs drops it before startBroker. // carries publishTls is refused: the scratch CA is for an in-process runtime only.
test('the bus process answers the publish ops from their own lines, not launch-op-refused', async (t) => { test('the bus process answers the publish ops from their own lines, not launch-op-refused', async (t) => {
const root = mkdtempSync(join(tmpdir(), 'bus-publish-ops-')); const root = mkdtempSync(join(tmpdir(), 'bus-publish-ops-'));
t.after(() => rmSync(root, { recursive: true, force: true })); t.after(() => rmSync(root, { recursive: true, force: true }));
@@ -260,7 +260,16 @@ test('the bus process answers the publish ops from their own lines, not launch-o
child.send(m); child.send(m);
return (await reply)[0]; return (await reply)[0];
}; };
const config = { dataRoot: root, businesses: businesses(), launches: [], readers: [], publishTls: ['-c', 'http.sslCAInfo=/nonexistent'] }; const config = { dataRoot: root, businesses: businesses(), launches: [], readers: [] };
const tlsChild = fork(new URL('../src/process.mjs', import.meta.url), [], { stdio: ['ignore', 'ignore', 'ignore', 'ipc'] });
t.after(() => {
if (tlsChild.exitCode === null) tlsChild.kill('SIGKILL');
});
const refused = once(tlsChild, 'message');
const gone = once(tlsChild, 'exit');
tlsChild.send({ op: 'boot', config: { ...config, publishTls: ['-c', 'http.sslCAInfo=/nonexistent'] } });
assert.deepEqual((await refused)[0], { ok: false, error: 'invalid-host-request' });
assert.equal((await gone)[0], 2);
assert.equal((await ask({ op: 'boot', config })).ok, true); assert.equal((await ask({ op: 'boot', config })).ok, true);
const unknown = 'not-a-ticket'; const unknown = 'not-a-ticket';
assert.deepEqual(await ask({ op: 'publishPush', ticket: unknown, id: 1 }), { ok: false, error: 'publish-unknown', id: 1 }); assert.deepEqual(await ask({ op: 'publishPush', ticket: unknown, id: 1 }), { ok: false, error: 'publish-unknown', id: 1 });