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

104 lines
4.7 KiB
Markdown

# 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.