fix(mcp): connecting an assistant asks for a recent sign-in or re-proof

Allowing an AI assistant hands it a standing credential to the record
that outlives the session, the same class of act as minting a token, but
the consent took a live session alone. A stolen session could connect
its own assistant and keep reading after the owner signed everything out.

The consent page now shows the Allow form only when the session signed in
or re-proved within five minutes (with the second factor on an account
that has one). Otherwise it links to a new confirm page that runs the
same re-proof dialog as the rest of the app and returns to the exact
consent request. The allow decision checks again and mints nothing
without it; deny needs no proof. The confirm page only ever returns to
the consent endpoint.
This commit is contained in:
Marc-André Bombeck committed 2026-09-26 22:38:40 +02:00
1 parent a294d7f684
commit 1900792756
14 files changed
+358 -3

No files matched your search

@@ -97,6 +97,13 @@ const TOKEN_ARM_COOKIE_PROOF_ROUTES = [
"app/api/fhir/Patient/$everything/route.ts",
].sort();
/**
* The connection consent. It resolves the session through `getSession`
* (OAuth answers in its own error format, not the envelope) and asks for the
* proof on the consent page and again on "allow".
*/
const CONSENT_PROOF_ROUTES = ["app/api/mcp/oauth/authorize/route.ts"];
/** Cookie-only routes that add the gate after their own resolver. */
const COOKIE_PROOF_ROUTES = [
"app/api/admin/backups/[id]/download/route.ts",
@@ -131,7 +138,11 @@ describe("the recent-proof gate", () => {
const found = callers("assertRecentCookieProof");
expect(found.length).toBeGreaterThan(0);
expect(found).toEqual(
[...COOKIE_PROOF_ROUTES, ...TOKEN_ARM_COOKIE_PROOF_ROUTES].sort(),
[
...COOKIE_PROOF_ROUTES,
...TOKEN_ARM_COOKIE_PROOF_ROUTES,
...CONSENT_PROOF_ROUTES,
].sort(),
);
});