mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-09 14:22:26 +00:00
Refuse to replace a live agent; clean a stale socket
AgentDaemon.start() guards both cases and neither was tested. Without the live check the second daemon deletes the socket and listens in its place — the first keeps running, holding unlocked seeds, with an expiry sweep nobody can reach and an audit log nobody is writing to. Silently orphaning a process that holds seeds is worse than failing to start, which is what makes it a refusal rather than a convenience. The other half matters too: a crashed agent leaves its socket behind, and treating that as "already running" would make the agent unstartable until someone deleted a file by hand. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
This commit is contained in:
1 parent
f487a001a2
commit
7a6b0a5f01
1 file changed
+41
@@ -662,6 +662,47 @@ describe("token-issue validates before it mints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("starting a daemon over an existing socket", () => {
|
||||
it("refuses to replace a LIVE agent", async () => {
|
||||
// Without this check the second daemon deletes the socket and listens in
|
||||
// its place. The first keeps running — holding unlocked seeds, with an
|
||||
// expiry sweep nobody can reach and an audit log nobody is writing to.
|
||||
// Silently orphaning a process that holds seeds is worse than failing to
|
||||
// start, which is what makes this a refusal rather than a convenience.
|
||||
const { d, sock } = await daemonAt(() => FROZEN);
|
||||
try {
|
||||
const second = new AgentDaemon(sock, 900, join(sock, ".."), () => FROZEN);
|
||||
await expect(second.start()).rejects.toThrow(/already running/);
|
||||
|
||||
// The original is still the one serving that socket.
|
||||
expect(await AgentClient.ping(sock)).toBe(true);
|
||||
const client = new AgentClient(sock);
|
||||
await client.put(FINGERPRINT, MNEMONIC, 900);
|
||||
expect(await client.ownerMnemonic(FINGERPRINT)).toBe(MNEMONIC);
|
||||
} finally {
|
||||
await d.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("cleans up a stale socket file rather than refusing forever", async () => {
|
||||
// The other half: a crashed agent leaves its socket behind, and treating
|
||||
// that as "already running" would make the agent unstartable until
|
||||
// someone deleted a file by hand.
|
||||
const dir = await mkdtemp(join(tmpdir(), "seedpass-stale-"));
|
||||
const sock = join(dir, "agent.sock");
|
||||
await writeFile(sock, "not a live socket");
|
||||
expect(await AgentClient.ping(sock)).toBe(false);
|
||||
|
||||
const d = new AgentDaemon(sock, 900, dir, () => FROZEN);
|
||||
await d.start();
|
||||
try {
|
||||
expect(await AgentClient.ping(sock)).toBe(true);
|
||||
} finally {
|
||||
await d.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("put refuses a half-supplied identity", () => {
|
||||
// `put` is the only door a seed comes through, and everything after it
|
||||
// trusts that both halves arrived. Mutation testing turned the `||` in its
|
||||
|
||||
Reference in new issue
Block a user