openclaw - 💡(How to fix) Fix openai-completions / openai-responses providers reject configured `baseUrl` on private IPs (SSRF policy)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When a user configures a generic OpenAI-compatible provider (api: "openai-completions" or api: "openai-responses") with a baseUrl whose hostname resolves to a private/CGNAT/loopback IP — the common case for LM Studio, Ollama, vLLM, or llama-server on a LAN or VM — every model call fails with:

[security] blocked URL fetch (url-fetch) targetOrigin=http://<private-ip>:<port>
  reason=Blocked hostname or private/internal/special-use IP address

The agent surfaces this as Connection error. and runEmbeddedAttemptWithBackend retries through the failover chain until chain_exhausted.

This contradicts the user's explicit trust signal (they typed the private baseUrl themselves) and breaks every self-hosted local-model setup.

Error Message

The agent surfaces this as Connection error. and runEmbeddedAttemptWithBackend retries through the failover chain until chain_exhausted. Actual: every turn fails with Connection error., gateway log shows the blocked URL fetch (url-fetch) warnings above.

Root Cause

When a user configures a generic OpenAI-compatible provider (api: "openai-completions" or api: "openai-responses") with a baseUrl whose hostname resolves to a private/CGNAT/loopback IP — the common case for LM Studio, Ollama, vLLM, or llama-server on a LAN or VM — every model call fails with:

[security] blocked URL fetch (url-fetch) targetOrigin=http://<private-ip>:<port>
  reason=Blocked hostname or private/internal/special-use IP address

The agent surfaces this as Connection error. and runEmbeddedAttemptWithBackend retries through the failover chain until chain_exhausted.

This contradicts the user's explicit trust signal (they typed the private baseUrl themselves) and breaks every self-hosted local-model setup.

Fix Action

Fix / Workaround

Workaround until merged

Anyone running a local model server — LM Studio, Ollama via a non-loopback baseUrl, vLLM, llama-server, mlx-lm serve, or a self-hosted gateway behind a private IP — is broken on first upgrade to 2026.5.x. Not edge case; this is the modal local-inference setup.

Code Example

[security] blocked URL fetch (url-fetch) targetOrigin=http://<private-ip>:<port>
  reason=Blocked hostname or private/internal/special-use IP address

---

"models": {
     "providers": {
       "lmstudio": {
         "baseUrl": "http://10.211.55.2:1234/v1",
         "api": "openai-completions",
         "apiKey": "lm-studio",
         "models": [{
           "id": "qwen/qwen3.6-35b-a3b",
           "name": "qwen/qwen3.6-35b-a3b",
           "input": ["text"],
           "contextWindow": 262144,
           "maxTokens": 8192,
           "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}
         }]
       }
     }
   }

---

// current
const fakeIpPolicy =
  typeof baseUrl === "string" && baseHostname && requestHostname === baseHostname
    ? ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist(baseUrl)
    : undefined;

---

const baseUrlAllowed =
  typeof baseUrl === "string" && baseHostname && requestHostname === baseHostname
    ? ssrfPolicyFromHttpBaseUrlAllowedHostname(baseUrl)
    : undefined;

const fakeIpPolicy = baseUrlAllowed
  ? ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist(baseUrl)
  : undefined;

// merge: explicit baseUrl trust + existing fake-IP pinning + explicit opt-in
return mergeSsrFPolicies(
  baseUrlAllowed,
  fakeIpPolicy,
  params.allowPrivateNetwork ? { allowPrivateNetwork: true } : undefined,
);
RAW_BUFFERClick to expand / collapse

Bug: generic openai-completions / openai-responses providers don't auto-allowlist their configured baseUrl hostname for SSRF

Version

  • OpenClaw 2026.5.10-beta.1 (4694211) — also reproducible on subsequent main commits
  • Node v24.13.0, Linux (Ubuntu 24.04, ARM64)

Summary

When a user configures a generic OpenAI-compatible provider (api: "openai-completions" or api: "openai-responses") with a baseUrl whose hostname resolves to a private/CGNAT/loopback IP — the common case for LM Studio, Ollama, vLLM, or llama-server on a LAN or VM — every model call fails with:

[security] blocked URL fetch (url-fetch) targetOrigin=http://<private-ip>:<port>
  reason=Blocked hostname or private/internal/special-use IP address

The agent surfaces this as Connection error. and runEmbeddedAttemptWithBackend retries through the failover chain until chain_exhausted.

This contradicts the user's explicit trust signal (they typed the private baseUrl themselves) and breaks every self-hosted local-model setup.

Why this is inconsistent with the rest of the codebase

Several named cloud providers already use the right pattern — they call ssrfPolicyFromHttpBaseUrlAllowedHostname(baseUrl) to auto-allowlist the configured hostname so the SSRF guard doesn't reject the operator's own endpoint:

  • src/image-generation/openai-compatible-image-provider.ts
  • src/tts/openai-compatible-speech-provider.ts
  • The chutes / huggingface / kilocode helpers in the models-*.js and provider-models-*.js bundles

The generic openai-completions / openai-responses builder in src/agents/provider-transport-fetch.ts does not do this. resolveModelTransportSsrFPolicy (line 388) only attaches ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist(baseUrl) (different helper, only relevant for DNS-pinned fake-IP flows) and otherwise relies on the user setting models.providers.<id>.request.allowPrivateNetwork: true manually.

That opt-in flag exists (src/config/zod-schema.core.ts:303 defines ConfiguredModelProviderRequestSchema with allowPrivateNetwork: z.boolean().optional()), is plumbed through provider-request-config.ts:335, and is documented in src/config/schema.help.ts:994 — but:

  1. It's effectively undiscoverable: the auto-discovery / onboard / models scan flows don't set it. openclaw doctor doesn't suggest it. Most users find the wall before they find the flag.
  2. The help text itself notes a remaining gap: "OpenAI Responses WebSocket reuses request for headers/TLS but does not use that fetch SSRF path" — so even with the flag set, openai-responses (the dialect that natively reports token usage and reasoning) has a second-layer wiring gap.

Reproduction

  1. Run any local OpenAI-compatible server on a private IP (LM Studio default http://127.0.0.1:1234, or on a Parallels NAT host like http://10.211.55.2:1234, or a tailnet host like http://100.x.x.x:1234).
  2. Configure in openclaw.json:
    "models": {
      "providers": {
        "lmstudio": {
          "baseUrl": "http://10.211.55.2:1234/v1",
          "api": "openai-completions",
          "apiKey": "lm-studio",
          "models": [{
            "id": "qwen/qwen3.6-35b-a3b",
            "name": "qwen/qwen3.6-35b-a3b",
            "input": ["text"],
            "contextWindow": 262144,
            "maxTokens": 8192,
            "cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0}
          }]
        }
      }
    }
  3. openclaw tui, send any prompt.
  4. Expected: model responds. Actual: every turn fails with Connection error., gateway log shows the blocked URL fetch (url-fetch) warnings above.

Direct curl http://10.211.55.2:1234/v1/chat/completions ... from the same host returns HTTP 200 in <500 ms, so the network / model is healthy — the block is purely OpenClaw's policy.

Proposed fix

One-line policy change, mirroring the pattern already used by the named providers.

In src/agents/provider-transport-fetch.ts, resolveModelTransportSsrFPolicy:

// current
const fakeIpPolicy =
  typeof baseUrl === "string" && baseHostname && requestHostname === baseHostname
    ? ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist(baseUrl)
    : undefined;

becomes (sketch):

const baseUrlAllowed =
  typeof baseUrl === "string" && baseHostname && requestHostname === baseHostname
    ? ssrfPolicyFromHttpBaseUrlAllowedHostname(baseUrl)
    : undefined;

const fakeIpPolicy = baseUrlAllowed
  ? ssrfPolicyFromHttpBaseUrlFakeIpHostnameAllowlist(baseUrl)
  : undefined;

// merge: explicit baseUrl trust + existing fake-IP pinning + explicit opt-in
return mergeSsrFPolicies(
  baseUrlAllowed,
  fakeIpPolicy,
  params.allowPrivateNetwork ? { allowPrivateNetwork: true } : undefined,
);

(mergeSsrFPolicies already exists — used in src/image-generation/openai-compatible-image-provider.ts:50.)

This means: when a user explicitly configures a model baseUrl, the hostname in that URL is added to allowedHostnames of the SSRF policy automatically, the same way named cloud providers already behave. The request.allowPrivateNetwork field remains available as an explicit override for edge cases (e.g., DNS hostname that resolves to a private IP and the operator wants the resolved-address check skipped too).

Secondary fix (separate commit in same PR)

Plumb the allowPrivateNetwork flag through src/agents/openai-ws-connection.ts so the openai-responses WebSocket transport honors the same operator opt-in described in src/config/schema.help.ts:994. The connection currently imports resolveProviderRequestPolicyConfig for TLS but ignores its allowPrivateNetwork resolution.

Tests

  • Add a regression test in src/agents/provider-transport-fetch.test.ts (or a sibling) that constructs a transport with baseUrl: "http://10.0.0.5:1234/v1" and asserts the resulting policy has allowedHostnames: ["10.0.0.5"] (or equivalent) — no opt-in required.
  • Add a parallel test that flips request.allowPrivateNetwork: true and asserts the policy carries allowPrivateNetwork: true.
  • A WS-level test (mirroring the structure of src/agents/openai-ws-connection.test.ts) that verifies the WS connection respects the same opt-in.

Workaround until merged

Set models.providers.<id>.request.allowPrivateNetwork: true on every locally-served provider. This works for openai-completions and is rejected at the WS layer for openai-responses (the secondary fix above).

Impact

Anyone running a local model server — LM Studio, Ollama via a non-loopback baseUrl, vLLM, llama-server, mlx-lm serve, or a self-hosted gateway behind a private IP — is broken on first upgrade to 2026.5.x. Not edge case; this is the modal local-inference setup.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix openai-completions / openai-responses providers reject configured `baseUrl` on private IPs (SSRF policy)