mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-05 20:32:05 +00:00
security: reject non-string credentials on the agent protocol
Found by running attacks against the socket rather than reading the code: a token supplied as a single-element array was accepted, because String(["abc"]) is "abc" and the comparison went through String() coercion. No privilege was gained -- the attacker still needed the real token -- but type coercion on a credential path is the kind of sloppiness that becomes a bug the moment the surrounding code changes. The handler now requires op, cap, token, fingerprint, id and sink to be strings, and command to be an array of strings, rejecting anything else outright. The same run confirmed the rest of the boundary behaves: by-id access to entries excluded by a token's label regex or kind constraint is refused server-side with the correct reason, reveal without the reveal scope is refused, denials do not consume uses, concurrent requests cannot overrun a one-use token, and a sink child spawned by the agent sees exactly one SEEDPASS_ variable -- the secret it was given. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
a1df3cfde0
commit
6b862baa71
3 files changed
+42
No files matched your search
@@ -326,6 +326,21 @@ export class AgentDaemon {
|
||||
|
||||
private async handle(msg: Record<string, unknown>): Promise<Record<string, unknown>> {
|
||||
this.expire();
|
||||
// Reject non-string credentials and identifiers rather than coercing
|
||||
// them. String(["abc"]) is "abc", so an array would sail through a
|
||||
// String() comparison; nothing good comes of accepting these shapes.
|
||||
for (const field of ["op", "cap", "token", "fingerprint", "id", "sink"]) {
|
||||
const value = msg[field];
|
||||
if (value !== undefined && typeof value !== "string") {
|
||||
return { ok: false, error: `field '${field}' must be a string` };
|
||||
}
|
||||
}
|
||||
if (msg["command"] !== undefined) {
|
||||
const command = msg["command"];
|
||||
if (!Array.isArray(command) || command.some((c) => typeof c !== "string")) {
|
||||
return { ok: false, error: "field 'command' must be an array of strings" };
|
||||
}
|
||||
}
|
||||
const op = String(msg["op"] ?? "");
|
||||
if (OWNER_OPS.has(op) && !this.isOwner(msg)) {
|
||||
// Do not reveal whether the profile exists or is unlocked.
|
||||
|
||||
Reference in new issue
Block a user