Remote-MCP: deny-by-default server-side authorization over the full request tuple, read-only by default #957

Open
opened 2026-07-28 02:52:04 -05:00 by jcwalker3 · 0 comments
Owner

Parent epic: #929.
Cross-link: #948, #955 (certification umbrella).
Depends on: #932, #934, #937, #956.

Goal supported: secure isolation — and it serves both goals.

Problem

#932 resolves a principal carrying profile, role kind, identity, remote, org, repository — and stops there. It states no deny-by-default posture and no read-only default. Authorization must also bind worker identity, session, capability, target, lease ownership, and exact head, or a correctly-authenticated caller can still act on someone else's lease or a stale head.

Current evidence

  • #932's scope and acceptance criteria enumerate six principal fields and no others.
  • #932 explicitly defers wire authentication to #938 and changes no permission sets.
  • Exact-head enforcement lives client-side today, in #916 and #896.
  • Lease ownership is enforced in the local lock store, not at a request boundary.

Scope

A single server-side authorization decision point evaluating the full tuple:

(worker_identity, session, principal, role, repository, capability, target, lease ownership, exact head)

  • Default deny on any unresolved element.
  • Default read-only, with mutation requiring an explicitly enumerated, narrowly-scoped grant.

Non-goals

  • Changing the permission sets per role.
  • Re-implementing #932's principal resolution.
  • Client-side gates.

Security and concurrency boundaries

This is the single mutation-authorization boundary for the remote deployment. No mutation path may reach Gitea without a verdict from it.

Acceptance criteria

  • Every tool dispatch passes the decision point.
  • An unresolved element denies.
  • Mutation is denied unless explicitly granted.
  • Lease-bound operations deny when the caller does not own the live lease.
  • Head-bound operations deny when the presented head is not the live head.
  • Every denial is structured, names the failing element, and mutates nothing.

Required positive tests

  • Each element present and valid permits the operation.
  • Read paths are permitted without a mutation grant.

Required negative and adversarial tests

  • One denial test per element of the tuple.
  • A caller presenting a valid principal but a foreign lease.
  • A valid principal at a stale head.
  • A read-only session attempting each mutation surface.
  • Concurrent requests with different tuples, proving no state leaks between decisions.

Deployment and migration requirements

Configuration-driven grant table, versioned.

Rollback and recovery requirements

The grant table revision is revertible without redeployment.

Observability and audit requirements

Every decision is audited with the failing element, never the credential.

Definition of done

No mutation path reaches Gitea without a decision-point verdict.

Parent epic: #929. Cross-link: #948, #955 (certification umbrella). Depends on: #932, #934, #937, #956. **Goal supported:** secure isolation — and it serves both goals. ## Problem #932 resolves a principal carrying profile, role kind, identity, remote, org, repository — and stops there. It states no deny-by-default posture and no read-only default. Authorization must also bind worker identity, session, capability, target, lease ownership, and exact head, or a correctly-authenticated caller can still act on someone else's lease or a stale head. ## Current evidence - #932's scope and acceptance criteria enumerate six principal fields and no others. - #932 explicitly defers wire authentication to #938 and changes no permission sets. - Exact-head enforcement lives client-side today, in #916 and #896. - Lease ownership is enforced in the local lock store, not at a request boundary. ## Scope A single server-side authorization decision point evaluating the full tuple: `(worker_identity, session, principal, role, repository, capability, target, lease ownership, exact head)` - Default deny on any unresolved element. - Default read-only, with mutation requiring an explicitly enumerated, narrowly-scoped grant. ## Non-goals - Changing the permission sets per role. - Re-implementing #932's principal resolution. - Client-side gates. ## Security and concurrency boundaries This is the single mutation-authorization boundary for the remote deployment. No mutation path may reach Gitea without a verdict from it. ## Acceptance criteria - Every tool dispatch passes the decision point. - An unresolved element denies. - Mutation is denied unless explicitly granted. - Lease-bound operations deny when the caller does not own the live lease. - Head-bound operations deny when the presented head is not the live head. - Every denial is structured, names the failing element, and mutates nothing. ## Required positive tests - Each element present and valid permits the operation. - Read paths are permitted without a mutation grant. ## Required negative and adversarial tests - One denial test per element of the tuple. - A caller presenting a valid principal but a foreign lease. - A valid principal at a stale head. - A read-only session attempting each mutation surface. - Concurrent requests with different tuples, proving no state leaks between decisions. ## Deployment and migration requirements Configuration-driven grant table, versioned. ## Rollback and recovery requirements The grant table revision is revertible without redeployment. ## Observability and audit requirements Every decision is audited with the failing element, never the credential. ## Definition of done No mutation path reaches Gitea without a decision-point verdict.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#957