mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-27 07:52:03 +00:00
Throw on a denied agent request instead of returning a quiet zero
Every owner-op client method checks `ok` and throws, and none of those checks
was tested. Removing one does not produce an error — it produces whatever the
failed response coerces to, and for each of these that is a value which reads
as a legitimate outcome:
lock() -> 0, printed as "0 profiles locked" while the seeds are
still resident and the user has been told the vault is
locked
highRiskLock() -> false, indistinguishable from "there was nothing to
lock"
highRiskStatus() -> { unlocked: false, expires_at: NaN }
A refusal that looks like a successful no-op is worse than an error, because
nobody retries it. Forced by pointing SEEDPASS_AGENT_CAP at the wrong
capability, which is also the shape of the real failure: a caller that has
lost owner authority.
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
d188005b10
commit
a9e31215ea
1 file changed
+31
@@ -712,6 +712,37 @@ describe("the wire protocol refuses shapes, not just values", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("a denied request is not a quiet zero", () => {
|
||||
it("throws instead of reporting a false success", async () => {
|
||||
// Every owner-op client method checks `ok` and throws. Drop that check
|
||||
// and the method returns whatever the failed response coerces to, which
|
||||
// for each of these is a value that reads as a legitimate outcome:
|
||||
//
|
||||
// lock() -> 0 "0 profiles locked" — but the seeds are
|
||||
// still resident, and the user was told the
|
||||
// vault was locked.
|
||||
// highRiskLock() -> false indistinguishable from "there was nothing
|
||||
// to lock".
|
||||
// highRiskStatus() -> { unlocked: false, expires_at: NaN }
|
||||
//
|
||||
// A refusal that looks like a successful no-op is worse than an error,
|
||||
// because nobody retries it.
|
||||
const { d, sock } = await daemonAt(() => FROZEN);
|
||||
const previous = process.env["SEEDPASS_AGENT_CAP"];
|
||||
process.env["SEEDPASS_AGENT_CAP"] = "not-the-capability";
|
||||
try {
|
||||
const client = new AgentClient(sock);
|
||||
await expect(client.lock()).rejects.toThrow(/owner capability/);
|
||||
await expect(client.highRiskLock(FINGERPRINT)).rejects.toThrow(/owner capability/);
|
||||
await expect(client.highRiskStatus(FINGERPRINT)).rejects.toThrow(/owner capability/);
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env["SEEDPASS_AGENT_CAP"];
|
||||
else process.env["SEEDPASS_AGENT_CAP"] = previous;
|
||||
await d.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user