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

  1. 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.
  2. If the endpoint is a remote/self-hosted server, switch the base URL to https:// (terminate TLS at the gateway).
  3. 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.
  4. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/9bb4e4286ae761a6. Report an issue: GitHub.