{"record":{"id":"94c6c5637ed61940","repo":"abhigyanpatwari/GitNexus","slug":"refusing-to-start-the-mcp-http-server-on-a-non-loo","errorCode":null,"errorMessage":"Refusing to start the MCP HTTP server on a non-loopback host (${host}) without authentication — it would expose all indexed repos to anyone who can reach it. Pass --auth-token (or set GITNEXUS_MCP_AUTH_TOKEN), or bind --host 127.0.0.1. This applies to --host 0.0.0.0 and --host :: as well.","messagePattern":"Refusing to start the MCP HTTP server on a non-loopback host \\((.+?)\\) without authentication — it would expose all indexed repos to anyone who can reach it\\. Pass --auth-token \\(or set GITNEXUS_MCP_AUTH_TOKEN\\), or bind --host 127\\.0\\.0\\.1\\. This applies to --host 0\\.0\\.0\\.0 and --host :: as well\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/mcp/http-transport.ts","lineNumber":477,"sourceCode":" * - POST /mcp        — Streamable HTTP (modern clients)\n * - GET /sse         — legacy SSE stream (old clients)\n * - POST /messages   — legacy SSE message endpoint\n *\n * @param backend   LocalBackend instance\n * @param options   Server configuration\n * @returns         The listening http.Server\n */\nexport async function startMcpHttpServer(\n  backend: LocalBackend,\n  options: McpHttpOptions,\n): Promise<HttpServer> {\n  const { port, host, authToken } = options;\n\n  // Refuse to start an unauthenticated server on a non-loopback interface — that\n  // would silently expose every indexed repo to anyone who can reach the host.\n  // Loopback binds stay open by default; non-loopback binds require a token.\n  if (!authToken && !isLoopbackHost(host)) {\n    throw new Error(\n      `Refusing to start the MCP HTTP server on a non-loopback host (${host}) without ` +\n        'authentication — it would expose all indexed repos to anyone who can reach it. ' +\n        'Pass --auth-token (or set GITNEXUS_MCP_AUTH_TOKEN), or bind --host 127.0.0.1. ' +\n        'This applies to --host 0.0.0.0 and --host :: as well.',\n    );\n  }\n\n  const repositoryPolicy = options.repositoryPolicy ?? (await createMcpRepositoryPolicy(backend));\n\n  const app: Express = express();\n\n  // Suppress X-Powered-By to reduce information leakage.\n  app.disable('x-powered-by');\n\n  // PNA (Chrome 130+ Private Network Access) preflight support.\n  // The browser sends `Access-Control-Request-Private-Network: true` ONLY on the\n  // CORS preflight (an OPTIONS request); emit the matching allow header only then,\n  // never on actual GET/POST responses. Runs before cors() so the header survives","sourceCodeStart":459,"sourceCodeEnd":495,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/mcp/http-transport.ts#L459-L495","documentation":"startMcpHttpServer refuses to bind a non-loopback host unless an auth token is configured — a deliberate security guard so exposing the HTTP MCP server cannot silently publish every indexed repo to anyone on the network. Loopback binds (127.0.0.1, ::1) stay open by default; 0.0.0.0, ::, LAN IPs, and hostnames all require --auth-token / GITNEXUS_MCP_AUTH_TOKEN.","triggerScenarios":"Starting the MCP HTTP server with --host 0.0.0.0 (common in Docker, where 127.0.0.1 makes the port unreachable from outside the container), --host ::, a LAN IP, or a resolvable hostname, without passing --auth-token or setting GITNEXUS_MCP_AUTH_TOKEN.","commonSituations":"Dockerizing GitNexus and switching the bind to 0.0.0.0 for port mapping; sharing an MCP instance across a team LAN; copying a loopback dev config into a deployment; Kubernetes sidecar deployments exposing the port.","solutions":["Provide a token: start with --auth-token <strong-secret> or export GITNEXUS_MCP_AUTH_TOKEN, and have clients send it per the server's auth scheme.","Or keep binding loopback: --host 127.0.0.1, and put an authenticating reverse proxy (nginx/Traefik) in front for remote access.","In Docker, bind 127.0.0.1 inside the container only if you use a proxy sidecar; otherwise pass the token and publish the port deliberately.","Never widen the bind to work around the error without also adding auth — the guard exists because the endpoint exposes all indexed repos."],"exampleFix":"# before: unauthenticated non-loopback bind (refuses to start)\n$ gitnexus serve --host 0.0.0.0 --port 4747\n# → Refusing to start the MCP HTTP server on a non-loopback host ...\n\n# after: authenticated non-loopback bind\n$ GITNEXUS_MCP_AUTH_TOKEN=$(openssl rand -hex 32) gitnexus serve --host 0.0.0.0 --port 4747\n# or stay loopback behind an auth proxy:\n$ gitnexus serve --host 127.0.0.1 --port 4747","handlingStrategy":"validation","validationCode":"// Resolve and validate bind config before starting the server\nfunction resolveMcpBind(opts: { host?: string; authToken?: string }): { host: string; authToken?: string } {\n  const host = opts.host ?? '127.0.0.1';\n  const authToken = opts.authToken ?? process.env.GITNEXUS_MCP_AUTH_TOKEN;\n  const loopback = ['127.0.0.1', '::1', 'localhost'].includes(host);\n  if (!loopback && !authToken) {\n    throw new Error('Refusing non-loopback bind without GITNEXUS_MCP_AUTH_TOKEN / --auth-token');\n  }\n  return { host, authToken };\n}","typeGuard":null,"tryCatchPattern":"try {\n  await startMcpHttpServer(backend, options);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('without authentication')) {\n    // intentional guard: fail deployment loudly, never bind quietly\n    failDeployment('MCP HTTP requires auth for non-loopback binds');\n  }\n  throw err;\n}","preventionTips":["Make GITNEXUS_MCP_AUTH_TOKEN a required env var in any deployment config that binds non-loopback.","Default to --host 127.0.0.1 and put an authenticating reverse proxy in front for remote access.","Add a config lint step that rejects 0.0.0.0/:: binds lacking a token.","Generate tokens with `openssl rand -hex 32`; never reuse dev secrets."],"tags":["mcp","http-server","security","auth-token","network-binding","docker"],"backgroundTag":"server-bind-requires-auth","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}