fix(coordinator): bind JWT caller to worker at claim (close quarantine bypass)

The trust tier was read off the caller-supplied worker_id, so a JWT user who
knew any trusted worker's id could claim as it — draining and poisoning the
trusted queue and bypassing the untrusted-worker quarantine entirely.

Claim now requires a JWT caller to own the worker it acts as; a shared-token
caller (lab operator) may still act as any worker. Claim is the sole grantor of
a lease, so this also protects the downstream heartbeat/result/failure paths.

Tests: reject claim as another user's worker; allow claim as own worker.
This commit is contained in:
Efremenko Arhip
2026-07-26 19:26:30 +03:00
parent 80ff72a0fe
commit 163cbe14bf
2 changed files with 46 additions and 0 deletions
+13
View File
@@ -6,6 +6,7 @@ import (
"github.com/google/uuid"
"github.com/emil28092005/SciMesh/coordinator/internal/authctx"
"github.com/emil28092005/SciMesh/coordinator/internal/domain"
)
@@ -51,6 +52,18 @@ func (uc *ClaimTask) Execute(ctx context.Context, in ClaimTaskInput) (*domain.Cl
if err != nil {
return nil, err
}
// Bind the caller to the worker it claims as. A JWT-authenticated
// volunteer may operate only its own workers; without this the trust
// tier would be read off a caller-supplied worker_id, letting anyone who
// knows a trusted worker's id claim as it and bypass the quarantine
// below. A shared-token caller (no requester) is a lab operator and may
// act as any worker, preserving the original behaviour.
if r, ok := authctx.From(ctx); ok {
if worker.OwnerID == nil || *worker.OwnerID != r.UserID {
// Don't disclose that another user's worker exists.
return nil, domain.ErrWorkerNotFound
}
}
// C1 quarantine: an untrusted volunteer worker may register but receives
// no tasks, because there is not yet (until quorum, C2) any way to verify
// its results. Report an empty queue rather than an error, so its poller