mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-06 04:42:35 +00:00
Refuse unrecognised token scopes and malformed sink commands over the wire
Both checks fail closed if removed, and both fail closed SILENTLY, which is the reason they exist. An unknown scope grants nothing downstream — tokenAllows asks for read, use or reveal by name — so a token issued with `["admin"]` simply never authorises anything. The operator gets a token back, believes they issued it, and finds out when the job using it is denied. The likely cases are near-misses: "Read", "reveal " with a trailing space, "write" by analogy with other systems. Naming the bad scope in the refusal is the whole value. The sink `command` shape check is the same shape of argument. Every malformed command already fails closed, because the allowlist is checked with parseCommandSpec against the SAME array that is later spawned — so the check and the spawn cannot disagree. But failing closed there means a TypeError or a nonsense command word, and this daemon's stated posture is that every caller is untrusted. Refuse the shape while it is still describable. 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
7a6b0a5f01
commit
d188005b10
1 file changed
+50
@@ -622,6 +622,34 @@ describe("token-issue validates before it mints", () => {
|
||||
expect(r["token"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("refuses a scope it does not recognise, naming it", async () => {
|
||||
// A bogus scope grants nothing downstream — tokenAllows asks for read,
|
||||
// use or reveal by name — so dropping this check fails closed. But it
|
||||
// fails closed SILENTLY: the operator gets a token back, believes they
|
||||
// issued it, and only finds out when the job using it is denied. And a
|
||||
// near-miss is the likely case: "Read", "reveal " with a space, "write"
|
||||
// by analogy with other systems.
|
||||
const base = {
|
||||
op: "token-issue",
|
||||
cap: ownerCap,
|
||||
fingerprint: FINGERPRINT,
|
||||
name: "bad-scope",
|
||||
ttl: 600,
|
||||
uses: 1,
|
||||
};
|
||||
for (const scopes of [["admin"], ["read", "write"], ["Read"], ["reveal "]]) {
|
||||
const r = await rawRequest({ ...base, scopes });
|
||||
expect(r["ok"]).toBe(false);
|
||||
expect(String(r["error"])).toContain("unknown scopes");
|
||||
expect(r["token"]).toBeUndefined();
|
||||
}
|
||||
|
||||
// All three real scopes together are fine, so this rejects the unknown
|
||||
// ones rather than anything about issuing a multi-scope token.
|
||||
const good = await rawRequest({ ...base, scopes: ["read", "use", "reveal"] });
|
||||
expect(good["ok"]).toBe(true);
|
||||
});
|
||||
|
||||
it("refuses a bare string where an array is required", async () => {
|
||||
// `"read"` and `["read"]` are easy to confuse when hand-writing the
|
||||
// protocol, and the difference is not cosmetic: a string `kinds` turns
|
||||
@@ -662,6 +690,28 @@ describe("token-issue validates before it mints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("the wire protocol refuses shapes, not just values", () => {
|
||||
it("insists a sink command is an array of strings", async () => {
|
||||
// The exec allowlist is checked with parseCommandSpec against the SAME
|
||||
// array that is later spawned, so a wrong shape cannot cause the check
|
||||
// and the spawn to disagree — every malformed shape fails closed. But
|
||||
// "fails closed" here means a TypeError or a nonsense command word, not
|
||||
// a refusal, and this daemon's stated posture is that every caller is
|
||||
// untrusted. Refuse the shape while it is still describable.
|
||||
for (const command of ["wc -c", 42, { cmd: "wc" }, ["wc", 5], [null]]) {
|
||||
const r = await rawRequest({
|
||||
op: "use-sink",
|
||||
fingerprint: FINGERPRINT,
|
||||
id: "1",
|
||||
sink: "exec",
|
||||
command,
|
||||
});
|
||||
expect(r["ok"]).toBe(false);
|
||||
expect(String(r["error"])).toContain("array of strings");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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