security(auth): consider binding Clerk azp in validateBearerToken

Author: koala73Created Sep 15, 2026Updated Sep 15, 2026
Labelsenhancementsecurity

Why

server/auth-session.ts validateBearerToken verifies issuer, signature, expiry, and audience when present. It never reads azp. No caller does. Clerk sets azp to the origin that minted a browser session token and recommends checking it against authorized parties.

Probe on 2026-09-15 against api/internal/mcp-grant-mint.ts with the real resolveClerkSession, remote JWKS, and grant HMAC: a bearer with a foreign azp and a foreign Origin header mints a grant (200, one Redis write). Cookie-only, wrong-key, wrong-issuer, and expired tokens all return 401 with no write.

Why not now

Minting needs a JWT for the victim's sub. No origin that can obtain one has been shown. The only hosts under worldmonitor.app outside the app are clerk. (the identity provider) and abacus. (self-hosted Umami on our own infrastructure). Binding azp on one handler protects nothing that the gateway and Convex do not also accept.

If it is done

  1. Do it once in validateBearerToken, not per handler.
  2. Fail open when azp is absent. Clerk omits it on non-browser tokens.
  3. Survey what azp desktop (tauri://localhost), preview (worldmonitor-*-eliewm.vercel.app), and the subdomain hosts carry before choosing the allowlist. Seed it from TRUSTED_RETURN_URL_ORIGINS.

Found during the 2026-09-15 security-validation batch (McpGrantMintOriginUnbound outcome, closed no-change).