Skip to content

OAuth slice 2: fail-closed checks on the OAuth path (disabled, suspended, must-change-password, flock scope, rate limit) #796

Description

@mforce

Slice 2 of #788. Depends on #795. This is the slice that does the security work.

An OAuth-authenticated request bypasses CredentialEpochMiddleware, where the app runs its per-request fail-closed checks for JWT callers. This slice must re-establish every one of those checks on the OAuth path. The OAuth path inherits none of them for free.

Checks that must run on every OAuth-authenticated request

Check Why Existing reference
User disabled A disabled worker's assistant must stop immediately CredentialEpochMiddleware reads DisabledAt
Farm suspended A suspended account's assistants must stop The same correlated read, Account.IsActive (#532)
Must-change-password When the app forces a user to change their password, it blocks everything except the change (#283). An assistant that kept working would bypass that gate
Flock scoping A worker assigned to two flocks must see only those two through their assistant (#388). This is the hazard #787 is open about. Make it deliberate
Rate limits An assistant calls far faster than a human. The limit must key on the shared IFixedWindowCounter (#543/#544), never a process-local limiter. A process-local limiter is the #271 blocker shape, which earlier derivations got wrong twice

Instant revocation

#788 chose reference tokens so that disconnecting an assistant takes effect on the next call. Verify this end to end. Revoke, then assert that the next request fails. This app does an uncached per-request credential read (#364) because revocation must not wait for an expiry.

Scheme coexistence

This will be the second authentication scheme the app has ever registered. Today there is only AddJwtBearer, with no AddPolicyScheme. Decide and document how the app routes a request to the right handler, and make sure the app cannot mistake an OAuth token for a session JWT or the reverse.

Guards

Per AGENTS.md, each check needs a test that states its red mutation. The test most likely to go wrong asserts FlockScope.IsUnrestricted instead of IsResolved. IsUnrestricted defaults to true, so a guard written against it passes while the hole stays open. docs/plans/770-mcp-server/02-guards.md documents that exact trap.

Done when

Each check has a test that fails when the check is removed, and a revoked token stops working on the next request.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiAPI/endpoint layerepic-788OAuth 2.1 authorization server for MCP (#788)priority:tier3Real product weight, real costsize:LSeveral days; wide blast radius or unresolved scopesliceThin vertical work item

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions