Skip to content

Gateway: trusted-proxy mode + allowLoopback=true rejects internal loopback callers despite valid gateway.auth.password (docs say fallback should work) #82607

@esqandil

Description

@esqandil

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

  1. Configure gateway.auth.mode: "trusted-proxy" with allowLoopback: true and any non-trivial requiredHeaders.
  2. Set gateway.auth.password (and optionally OPENCLAW_GATEWAY_PASSWORD env).
  3. Start gateway.
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions