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

52 lines
3.2 KiB
Markdown

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