From 2a232cc3abb8752db9742fa1e978b2c0ea2d9c9c Mon Sep 17 00:00:00 2001 From: Neil Date: Fri, 13 Nov 2026 03:38:39 -0800 Subject: [PATCH] fix(cloud/mcp): MCP approval page works in real browsers (referrer-policy same-origin; no-referrer made browsers send Origin: null, which the CSRF origin check refused) --- cloud/apps/sandbox-control/src/mcp/consent.ts | 5 ++++- cloud/apps/sandbox-control/test/mcp.test.ts | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cloud/apps/sandbox-control/src/mcp/consent.ts b/cloud/apps/sandbox-control/src/mcp/consent.ts index 05844e4dd79..30208575e93 100644 --- a/cloud/apps/sandbox-control/src/mcp/consent.ts +++ b/cloud/apps/sandbox-control/src/mcp/consent.ts @@ -63,7 +63,10 @@ function securityHeaders(formAction: string): Record { // Clickjacking: never framed. form-action also covers the 302 to the client after submit. 'x-frame-options': 'DENY', 'content-security-policy': `default-src 'none'; style-src 'unsafe-inline'; img-src 'none'; script-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self' ${formAction}`, - 'referrer-policy': 'no-referrer', + // Not no-referrer: under no-referrer browsers send `Origin: null` on the form POST, which the + // same-origin check in oauth.ts refuses, so every real approval failed. same-origin still sends + // no referrer (and so none of the query) to any other origin. + 'referrer-policy': 'same-origin', 'x-content-type-options': 'nosniff', 'cross-origin-opener-policy': 'same-origin' } diff --git a/cloud/apps/sandbox-control/test/mcp.test.ts b/cloud/apps/sandbox-control/test/mcp.test.ts index f9e35ac23a8..226e66ea0a3 100644 --- a/cloud/apps/sandbox-control/test/mcp.test.ts +++ b/cloud/apps/sandbox-control/test/mcp.test.ts @@ -425,6 +425,8 @@ describe('MCP authorization', () => { const page = await h.send(new Request(`${ORIGIN}/oauth/authorize?${h.authorizeParams(client.client_id, challenge)}`)) expect(page.headers.get('x-frame-options')).toBe('DENY') + // Browsers only send a real Origin on the approval POST when the page's policy allows it (END-PASS: no-referrer broke every browser). + expect(page.headers.get('referrer-policy')).toBe('same-origin') expect(page.headers.get('content-security-policy')).toContain("frame-ancestors 'none'") expect(page.headers.get('content-security-policy')).toContain('form-action \'self\' http://127.0.0.1:33418') expect(page.headers.get('set-cookie')).toMatch(/__Host-orca_mcp_csrf=\w+; Path=\/; Secure; HttpOnly; SameSite=Strict/)