Files
auth-portal/docs/mcp-revocations.md
T

3.2 KiB

MCP revocation delivery (default off)

This capability is not enabled by adding the code. Deploy and verify the MM v2 receiver first, using the existing separate MCP_PORTAL_WEBHOOK_SECRET. Then explicitly configure portal:

  • MCP_REVOCATIONS_ENABLED=true
  • MCP_REVOCATIONS_TARGET=https://<fixed-mm-host>/_internal/mcp/portal/revocations
  • MCP_REVOCATIONS_SECRET: the same separate random secret, at least32 characters.

Never reuse JWT or audit-ingest secrets. Do not put credentials/query/fragment in the target. Only canonical HTTPS is accepted, with certificate verification, no redirects and a3-second whole request deadline; request/response limit4KiB. The target fingerprint is pinned in SQLite; changing destination refuses startup until an explicit operator-reviewed migration. Rotation of the shared secret does not change queued event bodies or IDs.

Portal SQLite migration2 adds an outbox without a user FK. When enabled, SQLite synchronous=FULL is required before mutations. Actual access/admin/disable/delete, logout, single-session and revoke-all functions enqueue in their same synchronous transaction. Enqueue failure/cap4096 rolls back the mutation. Queue-full prevents further relevant security mutations until delivery recovers; rows are never silently expired/discarded. Default-off neither captures new events nor sends; it does not promise revocation while disabled. Existing pending rows remain.

Delivery is at least once, FIFO, one request at a time, at most4 events per tick; retry delay grows to60seconds. A valid200 {ok:true,duplicate:boolean} ack removes the exact row/body. Crash after MM commit but before ack causes safe redelivery with the same eventId/body. Sender shutdown aborts and awaits request close before SQLite closes. Logs contain only a fixed unavailable event, never secret/body/cause.

V2 signs timestamp + "." + exactQueuedBody with HMAC-SHA256. x-mm-timestamp is fresh delivery time, while body eventOccurredAt is the immutable original mutation time. Re-signing/retrying never advances the owner cutoff. MM applies monotonic owner cutoffs and connection/credential/grant/plan/profile effects only to claims issued at or before that event. Legacy three-field events remain accepted with their historical signed-timestamp cutoff; this sender always usesv2.

Legacy JWT iat has second resolution. A JWT minted in the event's same second can remain rejected; obtain a fresh login after the natural second boundary. No future iat, artificial millisecond JWT precision or claims-version authority is added. Logout/single-session revoke is owner-wide for MCP because current portal access JWTs have no session identifier. Password-only/name/email edits retain their existing semantics; this capability does not invent a new password/session policy.

Before enabling: use portal's locked dependencies for targeted DB tests and API/DB typecheck, then verify actual HTTPS and MM PostgreSQL behavior (old JWT rejected, fresh JWT after boundary preserved by late/out-of-order events, replay/conflict, outage/restart/drain). Source-only and mock-network tests are not that acceptance. SQLite backup restore is an explicit operator security operation; an outbox cannot recover mutations deliberately removed by restoring an older database snapshot.