abhigyanpatwari/GitNexus · error · Error

Insecure http:// LLM base URLs are only allowed for…

Error message

Insecure http:// LLM base URLs are only allowed for localhost/127.0.0.1 or hosts listed by --allow-insecure-connection / GITNEXUS_ALLOW_INSECURE_CONNECTION. Use https:// for remote endpoints (got ${parsed.origin})

What it means

The base URL uses http:// (plaintext) and its host is not localhost, 127.0.0.1, or ::1, and is not in the allowlist built from `--allow-insecure-connection` / GITNEXUS_ALLOW_INSECURE_CONNECTION. By default plaintext LLM traffic is restricted to loopback to prevent leaking API keys and to block SSRF against internal hosts; remote http:// endpoints must be explicitly opted into per hostname. The message shows parsed.origin (scheme+host+port, no credentials).

Solutions

  1. Best: switch the endpoint to https:// (add TLS/terminating proxy) — no allowlist needed
  2. Otherwise allowlist the exact host: `--allow-insecure-connection 192.168.1.10` or GITNEXUS_ALLOW_INSECURE_CONNECTION=llm.internal.corp
  3. If the server is actually on this machine, use http://localhost:<port> or http://127.0.0.1:<port> which are always allowed
  4. Confirm the allowlisted hostname matches the base URL host character-for-character (no port, no scheme, no path)

Example fix

# before
gitnexus wiki --provider custom --base-url http://192.168.1.10:8080/v1

# after
gitnexus wiki --provider custom --base-url http://192.168.1.10:8080/v1 --allow-insecure-connection 192.168.1.10
Defensive patterns

Strategy: validation

Validate before calling

function assertInsecureHttpAllowed(baseUrl: string, allowlist: readonly string[]): void {
  const u = new URL(baseUrl);
  if (u.protocol !== 'http:') return;
  const host = u.hostname.toLowerCase().replace(/^\[|\]$/g, '');
  if (host === 'localhost' || host === '127.0.0.1' || host === '::1') return;
  if (!allowlist.map(h => h.trim().toLowerCase()).includes(host)) {
    throw new Error(`http://${host} not allowlisted — use https or add --allow-insecure-connection ${host}`);
  }
}

Try / catch

try {
  validateLLMBaseUrl(baseUrl, allowHosts);
} catch (err) {
  if (err instanceof Error && err.message.includes('Insecure http:// LLM base URLs')) {
    // extract host via new URL(baseUrl).hostname and prompt user to allowlist it or upgrade to https
  }
}

Prevention

When it happens

Trigger: Setting `--base-url http://192.168.1.10:8080/v1` or `http://llm.internal.corp/v1` for a LAN/self-hosted LLM (Ollama, LiteLLM proxy, vLLM on another machine) without a matching allowlist entry; hostname comparison fails after normalization (host:port entries in the allowlist are rejected upstream, trailing-case mismatches are handled but a different host string is not).

Common situations: Self-hosted LLM on a LAN box or Docker host (http://host.docker.internal, NAS IP); corporate internal endpoint that is http-only; forgetting that the allowlist matches the hostname exactly, not a domain suffix.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@52924ef12c (2026-08-20). Data as JSON: /api/errors/44c6c6d7c9c42267. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/core/wiki/llm-client.ts:262

    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 52924ef12c)