abhigyanpatwari/GitNexus · error · Error
Insecure http:// LLM base URLs are only allowed for localhos
Error message
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}) What it means
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).
Source
Thrown at gitnexus/src/core/wiki/llm-client.ts:258
parsed = new URL(baseUrl);
} catch {
// Do not include the raw input in the message — it may contain credentials.
throw new Error('Invalid LLM base URL: must be a well-formed http:// or https:// URL');
}
if (!['https:', 'http:'].includes(parsed.protocol)) {
// Use parsed.protocol only (scheme), not the full URL, to avoid leaking credentials.
throw new Error(`LLM base URL must use http:// or https:// (got ${parsed.protocol})`);
}
if (parsed.protocol === 'http:') {
// Node's URL parser preserves IPv6 brackets in hostname (e.g. "[::1]"),
// so strip them before comparing to bare address literals.
const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '');
const allowedHosts = new Set(allowedInsecureHttpHosts.map(normalizeAllowedInsecureHttpHost));
if (host !== 'localhost' && host !== '127.0.0.1' && host !== '::1' && !allowedHosts.has(host)) {
// Use parsed.origin (scheme+host+port, no credentials) instead of the full URL.
throw new Error(
`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})`,
);
}
}
}
/**
* Returns true if the given base URL is an Azure OpenAI endpoint.
* Uses proper hostname matching to avoid spoofed URLs like
* "https://myresource.openai.azure.com.evil.com/v1".
*/
export function isAzureProvider(baseUrl: string): boolean {
try {
const { hostname } = new URL(baseUrl);
return hostname.endsWith('.openai.azure.com') || hostname.endsWith('.services.ai.azure.com');
} catch {View on GitHub (pinned to d540b00184)
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.
Example fix
// before
const baseUrl = 'http://10.0.0.5:8080/v1';
await callLLM(prompt, { baseUrl, apiKey, model });
// after (option A: TLS)
const baseUrl = 'https://10.0.0.5:8443/v1';
// after (option B: allowlist)
process.env.GITNEXUS_ALLOW_INSECURE_CONNECTION = '10.0.0.5';
const baseUrl = 'http://10.0.0.5:8080/v1'; Defensive patterns
Strategy: validation
Validate before calling
import { validateLLMBaseUrl, parseLLMAllowedInsecureHttpHosts } from 'gitnexus/dist/core/wiki/llm-client.js';
function safeBaseUrl(baseUrl, extraHosts = []) {
const allowed = [...parseLLMAllowedInsecureHttpHosts(process.env.GITNEXUS_ALLOW_INSECURE_CONNECTION), ...extraHosts];
validateLLMBaseUrl(baseUrl, allowed); // throws on bad scheme/host BEFORE any network
return baseUrl;
}
// call before buildRequestUrl / callLLM
safeBaseUrl(config.baseUrl); Type guard
function isInsecureHttpHostAllowed(baseUrl, allowed) {
try {
const u = new URL(baseUrl);
if (u.protocol !== 'http:') return true;
const host = u.hostname.toLowerCase().replace(/^\[|\]$/g, '');
return host === 'localhost' || host === '127.0.0.1' || host === '::1' || allowed.includes(host);
} catch { return false; }
} Try / catch
try { await callLLM(prompt, config); }
catch (e) {
if (/Insecure http:\/\//.test(e.message)) {
// prompt user to allowlist the host or switch to https
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Backend URL must use http:// or https:// (got ${parsed.proto
- LLM base URL must use http:// or https:// (got ${parsed.prot
- LLM request timed out after ${formatTimeoutDuration(config.r
- Invalid backend URL: must be a well-formed http:// or https:
- Invalid LLM base URL: must be a well-formed http:// or https
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/9bb4e4286ae761a6.
Report an issue: GitHub.