Feature/trusted proxy loopback#2181
Open
BingqingLyu wants to merge 4 commits intomainfrom
Open
Conversation
Allows loopback addresses (127.0.0.1, ::1) in trusted-proxy mode when explicitly enabled via config. Useful for same-pod or same-host deployments where agent and gateway share the same network namespace. - Add allowLoopback?: boolean to GatewayTrustedProxyConfig type - Add Zod validation for allowLoopback field - Modify authorizeTrustedProxy to check allowLoopback before rejecting - Add unit tests for allowLoopback behavior
* main: (522 commits) fix(browser): re-check interaction-driven navigations (openclaw#63226) test: reuse verbose directive reply imports test: reuse exec directive reply imports fix(browser): harden browser control override loading (openclaw#62663) Matrix: report startup failures as errors auth: persist explicit profile upserts directly test(doctor): mock memory-core runtime seam auth: avoid external cli sync on profile upsert feat: parallelize character eval runs fix: load QA live provider overrides build: stage nostr runtime dependencies fix(dotenv): block workspace runtime env vars (openclaw#62660) build: narrow plugin SDK declaration build test: harden Parallels macOS smoke fallback fix(memory): accept embedded dreaming heartbeat tokens test: harden provider mock isolation docs(config): tighten wording in reference test: reuse followup runner imports test: reuse image generate tool imports Align remote node exec event system messages with untrusted handling (openclaw#62659) ...
…nternal connections When auth.mode=trusted-proxy, internal subsystems (browser tool, sub-agents, CLI) connect via loopback without proxy headers, causing trusted_proxy_loopback_source and trusted_proxy_user_missing rejections. Add trustedProxy.allowLoopback to bypass the hard loopback rejection, trustedProxy.loopbackUser to auto-inject an operator identity for headerless loopback connections, and skip requiredHeaders checks for internal loopback connections using loopbackUser. Real proxy headers are always preferred. Closes openclaw#43300, closes openclaw#26007 Refs openclaw#4944, openclaw#16299, openclaw#50628
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
gateway.auth.mode=trusted-proxywithgateway.bind=lan, internal subsystems (browser tool, sub-agents, CLI) connect via loopback (ws://127.0.0.1:18789) and are hard-rejected bytrusted_proxy_loopback_sourcebeforetrustedProxiesis consulted. Even with127.0.0.1intrustedProxies, loopback connections are always rejected.sessions_spawn, Discord exec approvals, and all internalGatewayClientconnections are completely broken in Kubernetes/Docker deployments using trusted-proxy auth. This blocks the most common containerized deployment pattern (reverse proxy sidecar + trusted-proxy mode).trustedProxy.allowLoopbackto bypass the hard loopback rejection,trustedProxy.loopbackUserto auto-inject an operator identity for headerless loopback connections, and skiprequiredHeaderschecks for internal loopback connections usingloopbackUser. Real proxy headers are always preferred when present.GatewayClientURL resolution, browser tool code, or any other internal subsystem. This fix is entirely in the auth layer — internal clients still connect via loopback, but the auth layer now knows how to handle them.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
authorizeTrustedProxy()insrc/gateway/auth.tsunconditionally rejects all loopback connections withtrusted_proxy_loopback_sourcebefore consulting thetrustedProxieslist. This was introduced as a security hardening measure to prevent sidecars from forging identity headers on loopback, but it also blocks the gateway's own internal subsystems (browser tool, sub-agents, CLI) which legitimately connect from loopback without proxy headers.bind: lan+trustedProxies: ["${POD_IP}/32"]pattern works for the external proxy but breaks internal self-connections.Regression Test Plan (if applicable)
src/gateway/auth.test.tsallowLoopback: true(with proxy headers)loopbackUserwhenuserHeaderis missing on loopbackuserHeaderwhenloopbackUseris not set (fail-closed)userHeaderoverloopbackUseron loopbackallowLoopback: truetrusted_proxy_loopback_sourcetest retained — confirms default behavior unchanged.User-visible / Behavior Changes
gateway.auth.trustedProxy:allowLoopback: boolean(default:false) — allow loopback addresses in trusted-proxy modeloopbackUser: string(optional) — identity to assign to headerless loopback connectionsDiagram (if applicable)
Security Impact (required)
allowLoopbackrelaxes the loopback rejection for trusted-proxy modeallowLoopbackis opt-in (defaultfalse), so existing deployments are unaffected. When enabled, loopback connections are still subject to all other trusted-proxy checks (user header validation,allowUsersgating).loopbackUseronly fires when the user header is absent AND the connection is from loopback ANDallowLoopbackis true — it cannot be triggered from non-loopback sources. Real proxy headers always take precedence overloopbackUser.Repro + Verification
Environment
ghcr.io/openclaw/openclaw:2026.4.2in K8s StatefulSetSteps
auth.mode=trusted-proxy,bind=lan, oauth2-proxy sidecartrusted_proxy_loopback_sourcerejectionExpected
Actual (before fix)
[ws] unauthorized conn=... remote=127.0.0.1 reason=trusted_proxy_loopback_source[tools] browser failed: gateway closed (1008): unauthorizedEvidence
Human Verification (required)
pnpm vitest run src/gateway/auth.test.tsgreen.Review Conversations
Compatibility / Migration
allowLoopback: false,loopbackUser: undefined). No behavior change without explicit opt-in.gateway.auth.trustedProxy.Risks and Mitigations
allowLoopbackwithoutloopbackUserand expects headerless internal connections to work — they'll gettrusted_proxy_user_missing.loopbackUserexplains the dependency. Could add a startup warning whenallowLoopback=trueandloopbackUseris unset.loopbackUseridentity could be used to bypassallowUsersrestrictions if the operator forgets to add it to the list.allowUserscheck runs after user resolution — ifloopbackUseris not inallowUsers, the connection is still rejected.