fix(web): guard the app-origin port and the trusted-origin grant
Copilot review round 3 on #2100. - MCP_APP_ORIGIN_PORT could equal CLIENT_PORT or MCP_SANDBOX_PORT. The app-origin listener starts BEFORE the web server binds, so that is not a race it loses — it wins the port and the Inspector dies with EADDRINUSE. `CLIENT_PORT=6278` was a valid config before this feature existed. It now resolves to a dynamic port with a warning: of the three listeners this is the only one that can move, and refusing to boot would cost the user the whole Inspector over a feature they may not be using. - The proxy's same-origin refusal covered only its own origin. An app served from the INSPECTOR's origin is no safer: same-origin with the top document means direct access to window.top, its DOM and its storage, and the cross-origin frame in between buys nothing. Both trusted origins are now refused before allow-same-origin is granted. - Replaced the `as unknown as number` on `server.address()` with a real narrowing of the string/null members of its return type. - MCP_APP_ORIGIN_PORT added to the v1→v2 migration guide's env table, its Docker recipe, and a troubleshooting entry for the symptom this actually produces (the app renders, but its own backend rejects `Origin: null`). - clients/web/README.md now states which failure is outside the fallback guarantee — a published document the browser cannot reach — and why there is no honest signal to fall back on. Docs and code no longer disagree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
80f76e365e
commit
bb3ba91b8a
1 changed files with 118 additions and 14 deletions
|
|
@ -314,6 +314,42 @@ describe("buildWebServerConfigFromEnv", () => {
|
|||
expect(buildWebServerConfigFromEnv().appOriginPort).toBe(9300);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["the web server", "CLIENT_PORT", "the web server (CLIENT_PORT)"],
|
||||
[
|
||||
"the sandbox",
|
||||
"MCP_SANDBOX_PORT",
|
||||
"the MCP Apps sandbox (MCP_SANDBOX_PORT)",
|
||||
],
|
||||
])(
|
||||
"yields a dynamic app-origin port when it collides with %s",
|
||||
(_label, envVar, expectedReason) => {
|
||||
// The app-origin listener starts BEFORE the web server binds, so a
|
||||
// configured collision is not a race it loses — it WINS the port and the
|
||||
// Inspector then dies with EADDRINUSE. `CLIENT_PORT=6278` was a valid
|
||||
// config before this feature existed.
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
try {
|
||||
process.env[envVar] = "6500";
|
||||
process.env.MCP_APP_ORIGIN_PORT = "6500";
|
||||
expect(buildWebServerConfigFromEnv().appOriginPort).toBe(0);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(expectedReason),
|
||||
);
|
||||
} finally {
|
||||
warnSpy.mockRestore();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("leaves an explicit dynamic app-origin port alone", () => {
|
||||
// 0 means "OS-assigned" for both, so two zeros are not a collision.
|
||||
process.env.CLIENT_PORT = "6274";
|
||||
process.env.MCP_SANDBOX_PORT = "0";
|
||||
process.env.MCP_APP_ORIGIN_PORT = "0";
|
||||
expect(buildWebServerConfigFromEnv().appOriginPort).toBe(0);
|
||||
});
|
||||
|
||||
it("sets MCP_STORAGE_DIR when present", () => {
|
||||
process.env.MCP_STORAGE_DIR = "/tmp/storage";
|
||||
expect(buildWebServerConfigFromEnv().storageDir).toBe("/tmp/storage");
|
||||
|
|
|
|||
Loading…
Reference in a new issue