auth-yes/tasks/complete/2026-0825.01.jul.sec.auth-api.zero-trust-scope-guards-1805.md

4.7 KiB

TASK METADATA

  • Target Files: server/auth-session.ts, server/routes/sessions.ts, server/routes/events.ts, server/main.ts, ui/mod.ts, server/main.test.ts
  • Core Objective: Implement strict Zero-Trust scope enforcement and permission guards across all Auth-Yes internal API routes and SSR UI pages to ensure delegated and read-only sessions cannot escalate privileges, perform unauthorized mutations, or access admin interfaces.
  • Dependencies: None
  • Additional Important Notes: Delegated sessions (is_agent = true) must hold strictly the intersection of user permissions and assigned custom_scopes. Self-revocation of a delegated session is permitted, but revoking other sessions is prohibited without write:sessions or *.

Architectural Considerations & Risks

  • Risks:

    • Backward-Compatibility with Cached Sessions: Existing delegated sessions in the Valkey cache might lack explicit scopes if they were created before this strict enforcement. The middleware must default to a safe deny state for unknown or missing scopes but handle legacy is_agent = true sessions gracefully by denying mutating actions unless explicitly permitted.
    • Self-Revocation Edge Case: The guard logic must correctly distinguish between a session revoking itself (which is always allowed for clean logouts) and a session attempting to revoke a sibling or parent session.
    • Global Admin Resolution: The isGlobalAdmin check relies on database queries or cache. Ensure that the new middleware does not introduce significant latency by repeatedly querying PostgreSQL on every protected route. Use Valkey caching where possible.
  • Alternatives:

    • Instead of injecting customScopes into the Valkey cache session payload, we could perform a database lookup on every privileged API request to determine exact grants. However, this violates the Auth-Yes architectural principle of fast-path caching at the edge and would significantly degrade performance. The chosen approach of checking scopes from the cached AuthenticatedUser object is highly modular and performant.

Proposed Implementation

1. Scope Guard Helpers (server/auth-session.ts)

Implement reusable composable functions to evaluate the current user's capabilities.

  • hasScope(auth: AuthenticatedUser, requiredScope: string): boolean: Evaluates if auth.customScopes contains * or the requiredScope. If !auth.isAgent, returns true (primary sessions have all capabilities).
  • Create Hono middleware helpers:
    • requirePrimarySession(): Denies access with 403 Forbidden if auth.isAgent === true.
    • requireScope(scope: string): Denies access if hasScope(auth, scope) returns false.
    • requireAdmin(auth: AuthenticatedUser): Uses isGlobalAdmin(auth.userId) and verifies !auth.isAgent OR auth.customScopes.includes("*").

2. Guard Internal API Routes

Apply the newly created guards to critical endpoints.

  • server/routes/sessions.ts

    • DELETE /api/sessions/:id: Allow if target id === auth.sessionId. Otherwise, require write:sessions scope.
    • PUT /api/sessions/:id/scopes: Require admin or * scope, OR write:sessions.
    • POST /api/sessions/:id/extend: Require write:sessions or *.
    • POST /api/sessions/delegate: Prevent privilege escalation. Require admin or * scope to delegate an admin mode session. If creating a read_only or operator session, require the delegator to have at least those equivalent scopes.
  • server/routes/events.ts

    • POST /api/events: Require write:events or *.
    • POST /api/events/:id/end: Require write:events or *.
    • POST /api/events/:id/extend: Require write:events or *.
  • server/main.ts

    • Apply requirePrimarySession() or requireAdmin() to /api/admin/* and /api/passkeys/* endpoints.

3. SSR UI Gates (ui/mod.ts)

Guard the user interfaces against unauthorized delegated sessions.

  • /admin/*: Verify isAdmin && (!auth.isAgent || auth.customScopes.includes("*")). Redirect unauthorized sessions to /dashboard or return a 403 Forbidden page.
  • /dashboard/passkeys: Verify !auth.isAgent. Delegated sessions cannot manage passkeys.

4. Testing (server/main.test.ts)

  • Write tests that create a delegated session with only read_only scopes.
  • Assert that this session gets a 403 Forbidden when attempting to call POST /api/sessions/delegate, DELETE /api/sessions/:other_id, and POST /api/events.
  • Assert that the delegated session can successfully call DELETE /api/sessions/:own_id.
  • Write tests verifying that a delegated session attempting to access /admin/users is redirected/blocked.