feat(web): configurable public addresses for the MCP Apps listeners (#1862)
Add MCP_SANDBOX_FULL_ADDRESS and MCP_APP_ORIGIN_FULL_ADDRESS so a reverse proxy or ingress deployment can advertise the sandbox proxy URL and the _meta.ui.domain app origin the browser actually reaches, instead of the bind-derived http://localhost:<port> ones. The process still listens on MCP_SANDBOX_PORT / MCP_APP_ORIGIN_PORT. Both values are validated in public-address.ts and refused (warned, bind- derived address kept) when malformed or when their origin collapses onto the Inspector UI's (or, for the app origin, the sandbox's) — the MCP Apps spec requires host != sandbox origin, and the single-hostname proxy layout is exactly the one that breaks it. Plain http under an https UI is warned. The sandbox proxy page's referrer check was a hardcoded http://localhost|127.0.0.1 pattern no https embedder could match. It now compares the referrer's parsed origin against an exact list injected by the controller, derived from the same filtered allow-list as its frame-ancestors header, with the old loopback check kept only as the no-list fallback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzuzRPJv7M8wGe3wyw5sSg Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
3e728a3b8c
commit
12544d251c
1 changed files with 737 additions and 16 deletions
|
|
@ -76,8 +76,19 @@
|
|||
return allowList.join("; ");
|
||||
}
|
||||
|
||||
const ALLOWED_REFERRER_PATTERN =
|
||||
/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/;
|
||||
// The exact embedder origins this proxy accepts, injected by the sandbox
|
||||
// controller when it serves this page (see `embedderOriginsLiteral` in
|
||||
// server/sandbox-controller.ts). It is the same list the served
|
||||
// `frame-ancestors` header admits, so the two checks agree by
|
||||
// construction — an https or public-hostname Inspector page is accepted
|
||||
// exactly when the backend's ALLOWED_ORIGINS names it (#1862).
|
||||
//
|
||||
// `null` means the controller had no valid list (a hand-constructed
|
||||
// caller or a test); the header then falls back to the loopback family,
|
||||
// and so does this check.
|
||||
const ALLOWED_EMBEDDER_ORIGINS = /*INSPECTOR_EMBEDDER_ORIGINS*/ null;
|
||||
const LOOPBACK_EMBEDDER_PATTERN =
|
||||
/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
|
||||
|
||||
if (window.self === window.top) {
|
||||
throw new Error("This file is only to be used in an iframe sandbox.");
|
||||
|
|
@ -87,7 +98,22 @@
|
|||
throw new Error("No referrer, cannot validate embedding site.");
|
||||
}
|
||||
|
||||
if (!document.referrer.match(ALLOWED_REFERRER_PATTERN)) {
|
||||
// Compare the referrer's parsed ORIGIN, never the raw string: a pattern
|
||||
// over the full referrer is a prefix/substring match an attacker page's
|
||||
// path or query can be shaped to satisfy.
|
||||
let referrerOrigin = null;
|
||||
try {
|
||||
referrerOrigin = new URL(document.referrer).origin;
|
||||
} catch (e) {
|
||||
referrerOrigin = null;
|
||||
}
|
||||
|
||||
if (
|
||||
!referrerOrigin ||
|
||||
!(Array.isArray(ALLOWED_EMBEDDER_ORIGINS)
|
||||
? ALLOWED_EMBEDDER_ORIGINS.includes(referrerOrigin)
|
||||
: LOOPBACK_EMBEDDER_PATTERN.test(referrerOrigin))
|
||||
) {
|
||||
throw new Error(
|
||||
`Embedding domain not allowed in referrer ${document.referrer}. (Consider updating the validation logic to allow your domain.)`,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue