Summary
claim_task authorizes against the request body, not the stored row. The handler only checks the N13 body binding auth == body.assignee_did (crates/gitlawb-node/src/api/tasks.rs:175), then db.claim_task sets assignee_did=$2 on any pending row unconditionally (crates/gitlawb-node/src/db/mod.rs:3743-3757):
UPDATE agent_tasks SET status='claimed', assignee_did=$2, updated_at=$3
WHERE id=$1 AND status='pending'
complete_task/fail_task (tasks.rs:224, :277) then authorize against the stored assignee, so the new assignee of record passes them.
Impact
A signed caller other than the delegator's chosen assignee can take over a pending pre-assigned task and then legitimately complete or fail it. The schema (db/mod.rs:626-644) has no trigger or constraint preventing reassignment. Read-side exposure of the same objects is tracked in #268 and #395; this is the write-side ACL gap with a different root cause (N13 bound the caller to the body, never to the stored row).
Remediation
- Extend the claim predicate:
AND (assignee_did IS NULL OR assignee_did = $2).
- Return 403 (or 409) when a pre-assigned pending task is claimed by another identity.
- Integration test: a third-DID claim of a pre-assigned pending task must fail and not mutate the row.
Summary
claim_taskauthorizes against the request body, not the stored row. The handler only checks the N13 body bindingauth == body.assignee_did(crates/gitlawb-node/src/api/tasks.rs:175), thendb.claim_tasksetsassignee_did=$2on any pending row unconditionally (crates/gitlawb-node/src/db/mod.rs:3743-3757):complete_task/fail_task(tasks.rs:224,:277) then authorize against the stored assignee, so the new assignee of record passes them.Impact
A signed caller other than the delegator's chosen assignee can take over a pending pre-assigned task and then legitimately complete or fail it. The schema (
db/mod.rs:626-644) has no trigger or constraint preventing reassignment. Read-side exposure of the same objects is tracked in #268 and #395; this is the write-side ACL gap with a different root cause (N13 bound the caller to the body, never to the stored row).Remediation
AND (assignee_did IS NULL OR assignee_did = $2).