{"record":{"id":"9bb4e4286ae761a6","repo":"abhigyanpatwari/GitNexus","slug":"insecure-http-llm-base-urls-are-only-allowed-fo","errorCode":null,"errorMessage":"Insecure http:// LLM base URLs are only allowed for localhost/127.0.0.1 or hosts listed by --allow-insecure-connection / ${LLM_ALLOW_INSECURE_CONNECTION_ENV}. Use https:// for remote endpoints (got ${parsed.origin})","messagePattern":"Insecure http:// LLM base URLs are only allowed for localhost/127\\.0\\.0\\.1 or hosts listed by --allow-insecure-connection / (.+?)\\. Use https:// for remote endpoints \\(got (.+?)\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/wiki/llm-client.ts","lineNumber":258,"sourceCode":"    parsed = new URL(baseUrl);\n  } catch {\n    // Do not include the raw input in the message — it may contain credentials.\n    throw new Error('Invalid LLM base URL: must be a well-formed http:// or https:// URL');\n  }\n\n  if (!['https:', 'http:'].includes(parsed.protocol)) {\n    // Use parsed.protocol only (scheme), not the full URL, to avoid leaking credentials.\n    throw new Error(`LLM base URL must use http:// or https:// (got ${parsed.protocol})`);\n  }\n\n  if (parsed.protocol === 'http:') {\n    // Node's URL parser preserves IPv6 brackets in hostname (e.g. \"[::1]\"),\n    // so strip them before comparing to bare address literals.\n    const host = parsed.hostname.toLowerCase().replace(/^\\[|\\]$/g, '');\n    const allowedHosts = new Set(allowedInsecureHttpHosts.map(normalizeAllowedInsecureHttpHost));\n    if (host !== 'localhost' && host !== '127.0.0.1' && host !== '::1' && !allowedHosts.has(host)) {\n      // Use parsed.origin (scheme+host+port, no credentials) instead of the full URL.\n      throw new Error(\n        `Insecure http:// LLM base URLs are only allowed for localhost/127.0.0.1 ` +\n          `or hosts listed by --allow-insecure-connection / ${LLM_ALLOW_INSECURE_CONNECTION_ENV}. ` +\n          `Use https:// for remote endpoints (got ${parsed.origin})`,\n      );\n    }\n  }\n}\n\n/**\n * Returns true if the given base URL is an Azure OpenAI endpoint.\n * Uses proper hostname matching to avoid spoofed URLs like\n * \"https://myresource.openai.azure.com.evil.com/v1\".\n */\nexport function isAzureProvider(baseUrl: string): boolean {\n  try {\n    const { hostname } = new URL(baseUrl);\n    return hostname.endsWith('.openai.azure.com') || hostname.endsWith('.services.ai.azure.com');\n  } catch {","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/src/core/wiki/llm-client.ts#L240-L276","documentation":"Thrown by validateLLMBaseUrl() in gitnexus/src/core/wiki/llm-client.ts when an LLM base URL uses the plaintext http:// scheme but its host is not loopback (localhost/127.0.0.1/::1) and not in the allowlist. This is an SSRF guard (CWE-918): GitNexus refuses to send credentials-bearing LLM traffic over the open internet or to internal hosts unless the operator opted in. The allowlist is populated from --allow-insecure-connection on the CLI or the GITNEXUS_ALLOW_INSECURE_CONNECTION env var (comma-separated hostnames, host:port entries are rejected as confusing no-ops).","triggerScenarios":"config.baseUrl is an http:// URL whose normalized hostname (after stripping IPv6 brackets) is neither 'localhost', '127.0.0.1', '::1', nor present in allowedInsecureHttpHosts. Concretely: calling callLLM() with baseUrl='http://192.168.1.10:4000/v1' for a LAN LiteLLM proxy without setting GITNEXUS_ALLOW_INSECURE_CONNECTION=192.168.1.10; or pointing at 'http://10.0.0.5' for a self-hosted vLLM box.","commonSituations":"Running wiki generation against a LAN-hosted Ollama/LiteLLM/vLLM/openai-compatible server that only exposes http; copy-pasting a base URL that dropped the 's' in https; CI environments where the LLM gateway is http-only behind a VPN; IPv6 loopback written with brackets that did not normalize.","solutions":["If the endpoint is on this machine, point at a loopback address (http://localhost:PORT or http://127.0.0.1:PORT) — no allowlist needed.","If the endpoint is a remote/self-hosted server, switch the base URL to https:// (terminate TLS at the gateway).","If you must use plaintext http to a non-loopback host, allowlist it: set GITNEXUS_ALLOW_INSECURE_CONNECTION=hostname (comma-separated for several) or pass --allow-insecure-connection hostname. Use a bare hostname, not host:port.","Verify the scheme in the resolved URL — a trailing slash or missing protocol can cause new URL() to mis-parse the host."],"exampleFix":"// before\nconst baseUrl = 'http://10.0.0.5:8080/v1';\nawait callLLM(prompt, { baseUrl, apiKey, model });\n\n// after (option A: TLS)\nconst baseUrl = 'https://10.0.0.5:8443/v1';\n\n// after (option B: allowlist)\nprocess.env.GITNEXUS_ALLOW_INSECURE_CONNECTION = '10.0.0.5';\nconst baseUrl = 'http://10.0.0.5:8080/v1';","handlingStrategy":"validation","validationCode":"import { validateLLMBaseUrl, parseLLMAllowedInsecureHttpHosts } from 'gitnexus/dist/core/wiki/llm-client.js';\n\nfunction safeBaseUrl(baseUrl, extraHosts = []) {\n  const allowed = [...parseLLMAllowedInsecureHttpHosts(process.env.GITNEXUS_ALLOW_INSECURE_CONNECTION), ...extraHosts];\n  validateLLMBaseUrl(baseUrl, allowed); // throws on bad scheme/host BEFORE any network\n  return baseUrl;\n}\n// call before buildRequestUrl / callLLM\nsafeBaseUrl(config.baseUrl);","typeGuard":"function isInsecureHttpHostAllowed(baseUrl, allowed) {\n  try {\n    const u = new URL(baseUrl);\n    if (u.protocol !== 'http:') return true;\n    const host = u.hostname.toLowerCase().replace(/^\\[|\\]$/g, '');\n    return host === 'localhost' || host === '127.0.0.1' || host === '::1' || allowed.includes(host);\n  } catch { return false; }\n}","tryCatchPattern":"try { await callLLM(prompt, config); }\ncatch (e) {\n  if (/Insecure http:\\/\\//.test(e.message)) {\n    // prompt user to allowlist the host or switch to https\n  } else throw e;\n}","preventionTips":["Default to https:// base URLs; only use http:// for loopback.","Configure GITNEXUS_ALLOW_INSECURE_CONNECTION once in the environment for known LAN hosts.","Run validateLLMBaseUrl in a preflight check at startup, not only inside callLLM."],"tags":["llm","security","ssrf","configuration","url","wiki"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}