Summary
In gateway.auth.mode: "trusted-proxy" with allowLoopback: true, internal loopback clients that present a valid gateway.auth.password are rejected with trusted_proxy_missing_header_* before the documented "local direct fallback" path runs. The docs explicitly promise this path works; the runtime contradicts them.
Repro on 2026.5.12 (f066dd2, Node v22.22.2, Linux 6.8.0).
Expected (per docs)
docs/gateway/configuration-reference.md line 518:
Internal same-host callers can use gateway.auth.password as a local direct fallback; gateway.auth.token remains mutually exclusive with trusted-proxy mode.
docs/gateway/trusted-proxy-auth.md line 308:
Loopback trusted-proxy identity headers still fail closed: same-host callers are not silently authenticated as proxy users. Internal OpenClaw callers that bypass the proxy may authenticate with gateway.auth.password / OPENCLAW_GATEWAY_PASSWORD instead.
Same doc, line 101:
Forwarded-header evidence overrides loopback locality for local direct fallback. If a request arrives on loopback but carries X-Forwarded-For / X-Forwarded-Host / X-Forwarded-Proto headers pointing at a non-local origin, that evidence disqualifies local-direct password fallback…
Read together: a loopback connect with no forwarded headers and a valid auth.password should succeed via local-direct fallback even when auth.mode = "trusted-proxy".
Actual
Every internal loopback connect (backend RPC, CLI, subagent spawn, plugin approval channel) is rejected. The rejection log fields:
authMode=trusted-proxy
authProvided=password
authReason=trusted_proxy_missing_header_cf-access-jwt-assertion
client=gateway-client mode=backend hasDeviceIdentity=false
peer=127.0.0.1:44422->127.0.0.1:18789
fwd=n/a origin=n/a host=127.0.0.1:18789 ua=n/a
Note specifically:
authProvided=password — the client did send the configured password.
fwd=n/a — there are no X-Forwarded-* headers, so the "forwarded-header evidence overrides loopback locality" carve-out does NOT apply.
authReason=trusted_proxy_missing_header_* — the rejection comes from the trusted-proxy required-headers check.
CLI calls with hasDeviceIdentity=true (paired) fail the same way, so it isn't a device-identity issue.
Config (minimised)
{
"gateway": {
"mode": "local",
"auth": {
"mode": "trusted-proxy",
"trustedProxy": {
"userHeader": "cf-access-authenticated-user-email",
"requiredHeaders": [
"cf-access-jwt-assertion",
"x-forwarded-host",
"x-forwarded-proto"
],
"allowUsers": ["redacted"],
"allowLoopback": true
},
"password": "<32-byte hex>"
},
"trustedProxies": ["127.0.0.1/32", "::1/128"]
}
}
Plus OPENCLAW_GATEWAY_PASSWORD=<same value> exported into the gateway process via systemd EnvironmentFile=. Confirmed via /proc/<gateway-pid>/environ.
No gateway.auth.token set anywhere. No OPENCLAW_GATEWAY_TOKEN. No mixed-token startup error.
Setup context
Same-host cloudflared sits in front, terminating Cloudflare Access at nemoclaw.<host>.com and forwarding to 127.0.0.1:18789 with the four CF headers. Browser flow through cloudflared works. Only internal loopback callers (no CF headers) fail.
Auth pipeline order suspicion
The runtime appears to run trusted-proxy requiredHeaders validation before local-direct password fallback for all loopback connections. The authProvided=password field shows the password check would have succeeded; the trusted-proxy header check fires first and short-circuits the reply with trusted_proxy_missing_header_*. Per the docs, the order should be reversed (or at least: no-forwarded-headers loopback + password-supplied → password fallback wins).
Impact
sessions_spawn rejects with gateway closed (1008): unauthorized.
openclaw doctor, openclaw config get, and any CLI that opens a backend WS fails.
- Plugin approval channel can't be reached, so any tool call that would prompt for approval (e.g. R7/R8 gates) instead errors with
Plugin approval required (gateway unavailable).
- Cloudflare Tunnel browser access is unaffected (those requests carry the CF headers and pass).
Minimal repro
- Configure
gateway.auth.mode: "trusted-proxy" with allowLoopback: true and any non-trivial requiredHeaders.
- Set
gateway.auth.password (and optionally OPENCLAW_GATEWAY_PASSWORD env).
- Start gateway.
- From the same host, run any CLI that opens a backend WS, e.g.
openclaw config get gateway.auth.mode.
Expected: succeeds via password fallback.
Actual: gateway closed (1008): unauthorized, log shows authProvided=password authReason=trusted_proxy_missing_header_*.
Workarounds attempted
- Setting
OPENCLAW_GATEWAY_PASSWORD env var on the gateway process — no effect (password was already in config; this confirmed env path was wired).
openclaw doctor — same failure.
- Multiple gateway restarts (SIGUSR1 + full
systemctl restart) — same failure.
- Service reinstall via
openclaw service install — same failure.
Suggested fix direction
In the WS connect auth pipeline, when auth.mode = "trusted-proxy" and the inbound request is from loopback and carries no X-Forwarded-* evidence, route the request through the local-direct password path before running requiredHeaders validation. The current order seems to always run requiredHeaders first, which negates the documented fallback.
Alternative: make the fallback order explicit/configurable so this setup can be expressed without ambiguity.
Environment
- OpenClaw 2026.5.12 (
f066dd2)
- Node v22.22.2
- Linux 6.8.0-111-generic x64
- systemd user service (
openclaw-gateway.service)
- Loopback bind, same-host cloudflared front-door
Summary
In
gateway.auth.mode: "trusted-proxy"withallowLoopback: true, internal loopback clients that present a validgateway.auth.passwordare rejected withtrusted_proxy_missing_header_*before the documented "local direct fallback" path runs. The docs explicitly promise this path works; the runtime contradicts them.Repro on 2026.5.12 (
f066dd2, Node v22.22.2, Linux 6.8.0).Expected (per docs)
docs/gateway/configuration-reference.mdline 518:docs/gateway/trusted-proxy-auth.mdline 308:Same doc, line 101:
Read together: a loopback connect with no forwarded headers and a valid
auth.passwordshould succeed via local-direct fallback even whenauth.mode = "trusted-proxy".Actual
Every internal loopback connect (backend RPC, CLI, subagent spawn, plugin approval channel) is rejected. The rejection log fields:
Note specifically:
authProvided=password— the client did send the configured password.fwd=n/a— there are noX-Forwarded-*headers, so the "forwarded-header evidence overrides loopback locality" carve-out does NOT apply.authReason=trusted_proxy_missing_header_*— the rejection comes from the trusted-proxy required-headers check.CLI calls with
hasDeviceIdentity=true(paired) fail the same way, so it isn't a device-identity issue.Config (minimised)
{ "gateway": { "mode": "local", "auth": { "mode": "trusted-proxy", "trustedProxy": { "userHeader": "cf-access-authenticated-user-email", "requiredHeaders": [ "cf-access-jwt-assertion", "x-forwarded-host", "x-forwarded-proto" ], "allowUsers": ["redacted"], "allowLoopback": true }, "password": "<32-byte hex>" }, "trustedProxies": ["127.0.0.1/32", "::1/128"] } }Plus
OPENCLAW_GATEWAY_PASSWORD=<same value>exported into the gateway process via systemdEnvironmentFile=. Confirmed via/proc/<gateway-pid>/environ.No
gateway.auth.tokenset anywhere. NoOPENCLAW_GATEWAY_TOKEN. No mixed-token startup error.Setup context
Same-host cloudflared sits in front, terminating Cloudflare Access at
nemoclaw.<host>.comand forwarding to127.0.0.1:18789with the four CF headers. Browser flow through cloudflared works. Only internal loopback callers (no CF headers) fail.Auth pipeline order suspicion
The runtime appears to run trusted-proxy
requiredHeadersvalidation before local-direct password fallback for all loopback connections. TheauthProvided=passwordfield shows the password check would have succeeded; the trusted-proxy header check fires first and short-circuits the reply withtrusted_proxy_missing_header_*. Per the docs, the order should be reversed (or at least: no-forwarded-headers loopback + password-supplied → password fallback wins).Impact
sessions_spawnrejects withgateway closed (1008): unauthorized.openclaw doctor,openclaw config get, and any CLI that opens a backend WS fails.Plugin approval required (gateway unavailable).Minimal repro
gateway.auth.mode: "trusted-proxy"withallowLoopback: trueand any non-trivialrequiredHeaders.gateway.auth.password(and optionallyOPENCLAW_GATEWAY_PASSWORDenv).openclaw config get gateway.auth.mode.Expected: succeeds via password fallback.
Actual:
gateway closed (1008): unauthorized, log showsauthProvided=password authReason=trusted_proxy_missing_header_*.Workarounds attempted
OPENCLAW_GATEWAY_PASSWORDenv var on the gateway process — no effect (password was already in config; this confirmed env path was wired).openclaw doctor— same failure.systemctl restart) — same failure.openclaw service install— same failure.Suggested fix direction
In the WS connect auth pipeline, when
auth.mode = "trusted-proxy"and the inbound request is from loopback and carries noX-Forwarded-*evidence, route the request through the local-direct password path before runningrequiredHeadersvalidation. The current order seems to always runrequiredHeadersfirst, which negates the documented fallback.Alternative: make the fallback order explicit/configurable so this setup can be expressed without ambiguity.
Environment
f066dd2)openclaw-gateway.service)