#Openclaw localhost ssrf how to resolve it
1 messages · Page 1 of 1 (latest)
@magic tide
This depends which thing is hitting SSRF, but the safe answer is: don’t disable it globally unless you trust that network.
If this is the browser tool trying to open localhost / LAN IP, add a narrow browser SSRF exception in ~/.openclaw/openclaw.json:
{
browser: {
ssrfPolicy: {
allowedHostnames: ["localhost", "127.0.0.1"]
// hostnameAllowlist: ["my-internal-host.local"] // for named internal hosts
}
}
}
For a LAN/internal IP where hostname allowlisting isn’t enough, the broader opt-in is:
{
browser: {
ssrfPolicy: {
dangerouslyAllowPrivateNetwork: true
}
}
}
Use that only on a trusted personal machine/network, then restart the gateway. Docs: https://docs.openclaw.ai/tools/browser#configuration
If you mean accessing the OpenClaw dashboard/gateway remotely without public IP/domain, use an SSH tunnel or Tailscale instead of exposing it publicly:
ssh -N -L 18789:127.0.0.1:18789 user@host
Then open http://127.0.0.1:18789/ locally. Docs: https://docs.openclaw.ai/help/faq-first-run#how-do-i-authenticate-the-dashboard-on-localhost-vs-remote
Also please paste openclaw --version and the exact SSRF error if it still blocks — the right config key differs for browser vs model provider vs a channel webhook/media fetch. 🦞
i am using claude api and vllm for LLM and local model tool call need to updated the file
Yep — for Claude API as the main model + vLLM local model/tool-calling, the file to update is usually:
~/.openclaw/openclaw.json
First, please paste:
openclaw --version
Then check your vLLM baseUrl. If it is 127.0.0.1 / localhost, OpenClaw normally trusts loopback model endpoints automatically. If it is LAN/Tailscale/private DNS like 192.168.x.x, 10.x.x.x, *.local, or a tailnet hostname, add request.allowPrivateNetwork: true under that model provider.
Example shape:
{
models: {
mode: "merge",
providers: {
vllm: {
baseUrl: "http://127.0.0.1:8000/v1", // or your LAN/Tailscale URL
apiKey: "sk-local",
api: "openai-completions",
request: { allowPrivateNetwork: true },
timeoutSeconds: 300,
models: [
{
id: "your-vllm-model-id",
name: "Local vLLM Model",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 8192
}
]
}
}
},
agents: {
defaults: {
model: {
primary: "anthropic/claude-...",
small: "vllm/your-vllm-model-id"
}
}
}
}
Important: in models.providers.vllm.models[].id, use only the local model id — no vllm/ prefix there. The prefix is only used when selecting it, like vllm/your-vllm-model-id.
After editing:
openclaw gateway restart
openclaw status --all
Docs: https://docs.openclaw.ai/providers/vllm and https://docs.openclaw.ai/gateway/local-models
If you paste your redacted models + agents.defaults config and the exact SSRF error line, I can point to the exact key to change.
"agents": {
"defaults": {
"workspace": "~/.openclaw/wazuh-autopilot/workspace",
"model": {
"primary": "anthropic/claude-sonnet-4-5",
"fallbacks": [
"anthropic/claude-haiku-4-5"
]
},
// Model aliases for quick switching via /model command
"models": {
"anthropic/claude-sonnet-4-5": { "alias": "sonnet" },
"anthropic/claude-haiku-4-5": { "alias": "haiku" }
},
"sandbox": {
"mode": "all",
"scope": "session"
},
// Heartbeat: periodic health/sweep tasks using cheap model
"heartbeat": {
"every": "30m",
"model": "anthropic/claude-haiku-4-5"
},
// Memory search using embeddings
"memorySearch": {
"sources": ["memory", "sessions"],
"provider": "local",
"model": "/root/.cache/node-llama-cpp/models/embeddinggemma-300M.gguf"
},
"maxConcurrent": 3,
"timeoutSeconds": 600
},
"list": [
// =====================================================================
// TRIAGE AGENT — First responder for alert analysis
// =====================================================================
{
"id": "wazuh-triage",
"default": true,
"workspace": "~/.openclaw/wazuh-autopilot/agents/triage",
"agentDir": "~/.openclaw/wazuh-autopilot/agents/triage",
"tools": {
"profile": "minimal",
"alsoAllow": ["read", "edit", "web_fetch", "sessions_list", "sessions_history", "sessions_send"],
"deny": ["write", "exec", "delete", "browser"]
},
"heartbeat": { "every": "10m" }
}
You’re close — the missing part is a top-level models.providers.vllm block. Your pasted config only has agents, so OpenClaw knows about Claude aliases, but not your local vLLM provider/model.
Add this as a sibling to agents, not inside agents.defaults:
{
models: {
mode: "merge",
providers: {
vllm: {
baseUrl: "http://127.0.0.1:8000/v1", // change if your vLLM is elsewhere
apiKey: "sk-local",
api: "openai-completions",
request: { allowPrivateNetwork: true }, // needed for LAN/Tailscale/private endpoints; ok for local too
timeoutSeconds: 300,
models: [
{
id: "YOUR_VLLM_MODEL_ID",
name: "Local vLLM Model",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 8192
}
]
}
}
},
agents: {
defaults: {
model: {
primary: "anthropic/claude-sonnet-4-5",
fallbacks: ["anthropic/claude-haiku-4-5"]
},
models: {
"anthropic/claude-sonnet-4-5": { "alias": "sonnet" },
"anthropic/claude-haiku-4-5": { "alias": "haiku" },
"vllm/YOUR_VLLM_MODEL_ID": { "alias": "local" }
}
}
}
}
Key details:
- Replace
YOUR_VLLM_MODEL_IDwith exactly what vLLM returns fromGET /v1/models/openclaw models list --provider vllm. - In
models.providers.vllm.models[].id, do not includevllm/. - In
agents.defaults.models, do includevllm/, because that’s the selectable model ref. - Since you already set
agents.defaults.models, it acts as an allowlist — so the vLLM model must be added there or/model localwon’t work.
Then restart/check:
openclaw gateway restart
openclaw models list --provider vllm
openclaw status --all
Also paste openclaw --version if it still errors. Docs: https://docs.openclaw.ai/providers/vllm
i mean in openclaw agent when i use web_fetch here i need to used public ip or domain here i want to use localhost or internal ip
Ah — got it. For web_fetch specifically, localhost/internal IPs are blocked by design. There isn’t a normal tools.web.fetch.allowPrivateNetwork: true equivalent for web_fetch.
The config you may have seen here:
tools: {
web: {
fetch: {
ssrfPolicy: {
allowRfc2544BenchmarkRange: true,
allowIpv6UniqueLocalRange: true
}
}
}
}
is only for special fake-IP proxy ranges (198.18.0.0/15, fc00::/7). It does not allow localhost, 127.0.0.1, 10.x, 192.168.x, etc.
For your use case, use the browser tool instead, because browser has the private-network opt-in:
{
browser: {
enabled: true,
ssrfPolicy: {
allowedHostnames: ["localhost", "127.0.0.1"],
// or, for LAN/internal IPs you intentionally trust:
dangerouslyAllowPrivateNetwork: true
}
}
}
And your agent currently denies browser:
deny: ["write", "exec", "delete", "browser"]
So change that agent tools block to allow browser, e.g.:
tools: {
profile: "minimal",
alsoAllow: ["read", "edit", "web_fetch", "browser", "sessions_list", "sessions_history", "sessions_send"],
deny: ["write", "exec", "delete"]
}
Then restart the gateway:
openclaw gateway restart
Docs: https://docs.openclaw.ai/tools/web-fetch#limits-and-safety and https://docs.openclaw.ai/tools/browser#configuration
If you specifically need web_fetch to support trusted internal URLs, that’s a feature request / security-policy change — file it here: https://github.com/openclaw/openclaw/issues
Also still worth pasting openclaw --version so we know which config schema you’re on. 🦞
for web_fetch is there any temporary by pass
For web_fetch itself: no, there’s no temporary “allow localhost/internal IP” bypass today.
That’s intentional: web_fetch is treated like arbitrary URL fetching, so OpenClaw keeps loopback/LAN/metadata IPs blocked to avoid SSRF. The only web_fetch.ssrfPolicy exceptions are for special proxy fake-IP ranges, not normal localhost / 192.168.x / 10.x.
Practical temporary options:
- Use
browserinstead for internal pages, with:
browser: {
ssrfPolicy: {
allowedHostnames: ["localhost", "127.0.0.1"]
// or dangerouslyAllowPrivateNetwork: true for trusted LAN/internal access
}
}
- Expose the internal service through a safe tunnel / domain and fetch that public HTTPS URL.
- If this is for a very controlled local automation agent, allow a shell tool and have it use
curl— but that’s a different security tradeoff, and I’d keep it tightly scoped rather than broad.
So: no hidden config flag for web_fetch localhost. If you need that exact capability, it should be a GitHub issue/feature request: https://github.com/openclaw/openclaw/issues
how to achive same thing with curl
MANDATORY: Update Case Status via API
After completing triage, you MUST invoke the web_fetch tool to advance the pipeline. If you skip this step, the pipeline stalls and no downstream agents are triggered.
Invoke the web_fetch tool with the following URL (replace {case_id} with the actual case ID from the webhook message, and use your xxxxxx token):
web_fetch(url="http://<PUBLIC_IP>:9091/api/agent-action/update-case?case_id={case_id}&status=triaged&token=xxxxx")
this is my agent Agents.md file content right now its with web_fetch how to achive same with curl what to change in Agents.md and openclaw.json
Yes — use exec + curl, not web_fetch.
Important tradeoff: exec is a shell surface, so keep it narrowly scoped. Don’t enable full unrestricted exec for a public/support-facing agent if you can avoid it.
In openclaw.json, change your triage agent tools from this:
tools: {
profile: "minimal",
alsoAllow: ["read", "edit", "web_fetch", "sessions_list", "sessions_history", "sessions_send"],
deny: ["write", "exec", "delete", "browser"]
}
to something like this:
tools: {
profile: "minimal",
alsoAllow: ["read", "edit", "exec", "process", "sessions_list", "sessions_history", "sessions_send"],
deny: ["write", "delete", "browser"],
exec: {
host: "gateway", // important if localhost is on the OpenClaw host
security: "allowlist", // safer than full
ask: "on-miss",
timeoutSec: 30
}
}
Why host: "gateway": with your sandbox config, default exec may run inside the sandbox/container. In that case localhost means the sandbox, not your API service. gateway makes curl run from the OpenClaw host.
Then update your agent AGENTS.md instruction from web_fetch(...) to exec:
## MANDATORY: Update Case Status via API
After completing triage, you MUST invoke the `exec` tool to advance the pipeline.
Do not use `web_fetch` for this step; localhost/internal URLs are blocked there.
Run this curl command, replacing CASE_ID with the actual case id:
curl -fsS --get "http://127.0.0.1:9091/api/agent-action/update-case" \
--data-urlencode "case_id=CASE_ID" \
--data-urlencode "status=triaged" \
--data-urlencode "token=xxxxx"
If curl exits non-zero, report the failure and do not claim the case was updated.
If the API is on another internal host, use e.g.:
curl -fsS --get "http://192.168.1.50:9091/api/agent-action/update-case" \
--data-urlencode "case_id=CASE_ID" \
--data-urlencode "status=triaged" \
--data-urlencode "token=xxxxx"
Safer token version: put the token in a file like /home/YOURUSER/.openclaw/wazuh-autopilot/case-update.token, chmod 600, then use:
curl -fsS --get "http://127.0.0.1:9091/api/agent-action/update-case" \
--data-urlencode "case_id=CASE_ID" \
--data-urlencode "status=triaged" \
--data-urlencode "token@/home/YOURUSER/.openclaw/wazuh-autopilot/case-update.token"
After config change:
openclaw gateway restart
openclaw status --all
Docs: https://docs.openclaw.ai/tools/exec and https://docs.openclaw.ai/tools/exec-approvals
// ---------------------------------------------------------------------------
// GLOBAL TOOL POLICY
// ---------------------------------------------------------------------------
"tools": {
"profile": "minimal",
"alsoAllow": ["read", "web_fetch", "sessions_list", "sessions_history", "sessions_send"],
"deny": ["browser", "canvas"],
"sandbox": {
"tools": {
"allow": ["group:runtime", "group:fs", "group:sessions", "group:memory", "group:web"],
}
},
"web": {
"search": { "enabled": false },
"fetch": { "enabled": true }
}
},
need to change anything globally
Yes — if you want the agent to use curl, add exec globally or only on that triage agent. Since your global profile is minimal, I’d add it explicitly.
Change global tools to include exec + process, and add an exec policy:
"tools": {
"profile": "minimal",
"alsoAllow": [
"read",
"web_fetch",
"exec",
"process",
"sessions_list",
"sessions_history",
"sessions_send"
],
"deny": ["browser", "canvas"],
"exec": {
"host": "gateway",
"security": "allowlist",
"ask": "on-miss",
"timeoutSec": 30
},
"sandbox": {
"tools": {
"allow": ["group:runtime", "group:fs", "group:sessions", "group:memory", "group:web"]
}
},
"web": {
"search": { "enabled": false },
"fetch": { "enabled": true }
}
}
Main point: host: "gateway" makes curl http://127.0.0.1:9091/... run from the OpenClaw host. If it runs inside sandbox, localhost may point at the sandbox instead.
For your triage agent, also remove exec from its deny list and add it to alsoAllow:
"alsoAllow": ["read", "edit", "exec", "process", "sessions_list", "sessions_history", "sessions_send"],
"deny": ["write", "apply_patch", "browser", "canvas"]
Then restart:
openclaw gateway restart
One note: security: "allowlist" means first run may ask approval. If this is a fully unattended pipeline, you’ll need to allowlist that exact curl command via exec approvals; don’t set security: "full" unless this agent is isolated/trusted.
i dont want approval.
Got it. Don’t use security: "full" unless this is a totally private throwaway box — that gives the agent no-approval shell access.
Better no-approval setup: allowlist one wrapper script, then set exec to allowlist + ask off. That means the pipeline won’t pause, but random shell commands still won’t run.
1) Add exec globally
"tools": {
"profile": "minimal",
"alsoAllow": [
"read",
"web_fetch",
"exec",
"process",
"sessions_list",
"sessions_history",
"sessions_send"
],
"deny": ["browser", "canvas"],
"exec": {
"host": "gateway",
"security": "allowlist",
"ask": "off",
"timeoutSec": 30
},
"sandbox": {
"tools": {
"allow": ["group:runtime", "group:fs", "group:sessions", "group:memory", "group:web"]
}
},
"web": {
"search": { "enabled": false },
"fetch": { "enabled": true }
}
}
2) Remove exec from the triage agent deny list
"alsoAllow": ["read", "edit", "exec", "process", "sessions_list", "sessions_history", "sessions_send"],
"deny": ["write", "apply_patch", "browser", "canvas"]
3) Create a tiny wrapper script
Example path:
mkdir -p ~/.openclaw/wazuh-autopilot/bin
nano ~/.openclaw/wazuh-autopilot/bin/update-case-status
chmod 700 ~/.openclaw/wazuh-autopilot/bin/update-case-status
Script:
#!/usr/bin/env bash
set -euo pipefail
case_id="${1:?case_id required}"
status="${2:-triaged}"
token="xxxxx"
curl -fsS --get "http://127.0.0.1:9091/api/agent-action/update-case" \
--data-urlencode "case_id=${case_id}" \
--data-urlencode "status=${status}" \
--data-urlencode "token=${token}"
Use your internal IP instead of 127.0.0.1 if the API is on another host.
4) Allowlist only that script, no prompts
openclaw approvals allowlist add --gateway --agent wazuh-triage "$HOME/.openclaw/wazuh-autopilot/bin/update-case-status"
openclaw config set tools.exec.host gateway
openclaw config set tools.exec.security allowlist
openclaw config set tools.exec.ask off
openclaw gateway restart
5) Update AGENTS.md instruction
After completing triage, run the exec tool:
~/.openclaw/wazuh-autopilot/bin/update-case-status CASE_ID triaged
Replace CASE_ID with the actual case id. If the command fails, report the failure and do not claim the case was updated.
If you truly want unrestricted no-approval exec, the setting is security: "full", ask: "off", but I strongly would not use that for this agent. The wrapper + allowlist gives you unattended pipeline updates without handing the agent a blank shell check. 🦞
no need of helper i can use full instead
You can, yes — that’s the no-approval mode.
Global openclaw.json:
"tools": {
"profile": "minimal",
"alsoAllow": [
"read",
"web_fetch",
"exec",
"process",
"sessions_list",
"sessions_history",
"sessions_send"
],
"deny": ["browser", "canvas"],
"exec": {
"host": "gateway",
"security": "full",
"ask": "off",
"timeoutSec": 30
},
"sandbox": {
"tools": {
"allow": ["group:runtime", "group:fs", "group:sessions", "group:memory", "group:web"]
}
},
"web": {
"search": { "enabled": false },
"fetch": { "enabled": true }
}
}
For the wazuh-triage agent, make sure exec is not denied:
"tools": {
"profile": "minimal",
"alsoAllow": ["read", "edit", "exec", "process", "sessions_list", "sessions_history", "sessions_send"],
"deny": ["write", "apply_patch", "browser", "canvas"],
"exec": {
"host": "gateway",
"security": "full",
"ask": "off",
"timeoutSec": 30
}
}
Then restart:
openclaw gateway restart
Update AGENTS.md from web_fetch to something like: