Closes #1795. Enforce localhost-only binding in code (the "never 0.0.0.0" rule was only a spec note): refuse an all-interfaces HOST — across every spelling the OS binds as the wildcard (inet_aton decimal/octal/hex, IPv6, IPv4-mapped, zone-scoped, IDNA/Unicode) — unless DANGEROUSLY_BIND_ALL_INTERFACES=true (Docker's sanctioned opt-in). Fixes the reported stdio-connect 403 by expanding the default loopback origin allow-list to all three interchangeable forms. Hardening surfaced across an extended review: closes an IDNA/fullwidth wildcard bypass, a blank-ALLOWED_ORIGINS fail-open, and the CLI/TUI OAuth-callback wildcard bind (loopback-only now, RFC 8252); canonicalizes the origin allow-list the way a browser does; derives frame-ancestors from the real embedder list (IPv6-literal exclusion verified in headless Chromium); strictPort on all three bind points; and preserves the CLI exit-code map + JSON envelope through the mcp-inspector --cli launcher path. Shared host normalization lives in core/node/hostUrl.ts. Docs + tests + smokes throughout. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
37 lines
1.3 KiB
JavaScript
37 lines
1.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { resolve } from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { runTui } from "./tui.js";
|
|
|
|
export { runTui };
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const isMain =
|
|
process.argv[1] !== undefined &&
|
|
resolve(process.argv[1]) === resolve(__filename);
|
|
|
|
if (isMain) {
|
|
runTui(process.argv).catch((err: unknown) => {
|
|
// Print the message, not the stack — a startup config error (e.g. the
|
|
// OAuth callback loopback guard) should read as an actionable message, not
|
|
// an internal fault. Matches run-web.ts's house pattern. The stack is still
|
|
// available under DEBUG / MCP_DEBUG for a real fault.
|
|
console.error("Error:", err instanceof Error ? err.message : err);
|
|
// Append the stack only when DEBUG / MCP_DEBUG is *meaningfully* set — "0" /
|
|
// "false" / empty read as off, so a stray DEBUG=0 doesn't force a stack.
|
|
// (Duplicated from the launcher's `wantsDebugStack` — the two bins can't
|
|
// import each other; keep them in sync if this logic changes.)
|
|
const debugOn = (v: string | undefined): boolean => {
|
|
const s = v?.trim().toLowerCase();
|
|
return !!s && s !== "0" && s !== "false";
|
|
};
|
|
if (
|
|
(debugOn(process.env.MCP_DEBUG) || debugOn(process.env.DEBUG)) &&
|
|
err instanceof Error
|
|
) {
|
|
console.error(err.stack);
|
|
}
|
|
process.exit(1);
|
|
});
|
|
}
|