Skip to content

Feature/trusted proxy loopback#63379

Closed
mrosmarin wants to merge 4 commits intoopenclaw:mainfrom
mrosmarin:feature/trusted-proxy-loopback
Closed

Feature/trusted proxy loopback#63379
mrosmarin wants to merge 4 commits intoopenclaw:mainfrom
mrosmarin:feature/trusted-proxy-loopback

Conversation

@mrosmarin
Copy link
Copy Markdown

Summary

  • Problem: When gateway.auth.mode=trusted-proxy with gateway.bind=lan, internal subsystems (browser tool, sub-agents, CLI) connect via loopback (ws://127.0.0.1:18789) and are hard-rejected by trusted_proxy_loopback_source before trustedProxies is consulted. Even with 127.0.0.1 in trustedProxies, loopback connections are always rejected.
  • Why it matters: Browser tool snapshots, sessions_spawn, Discord exec approvals, and all internal GatewayClient connections 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).
  • What changed: Added 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 when present.
  • What did NOT change (scope boundary): No changes to the GatewayClient URL 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)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: authorizeTrustedProxy() in src/gateway/auth.ts unconditionally rejects all loopback connections with trusted_proxy_loopback_source before consulting the trustedProxies list. 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.
  • Missing detection / guardrail: No distinction between external callers on loopback (potentially forging headers) and internal backend callers (the gateway's own subsystems). No config knob to opt in to loopback trust for same-pod deployments.
  • Contributing context: Kubernetes pod networking places all containers (gateway, oauth2-proxy, chromium, terminal) in the same network namespace. Internal connections are always loopback. The bind: lan + trustedProxies: ["${POD_IP}/32"] pattern works for the external proxy but breaks internal self-connections.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/gateway/auth.test.ts
  • Scenario the test should lock in: 6 new test cases:
    1. Allows loopback when allowLoopback: true (with proxy headers)
    2. Injects loopbackUser when userHeader is missing on loopback
    3. Rejects loopback without userHeader when loopbackUser is not set (fail-closed)
    4. Prefers real userHeader over loopbackUser on loopback
    5. Rejects same-host proxy request with missing required header
    6. Allows same-host proxy request when allowLoopback: true
  • Why this is the smallest reliable guardrail: All three code paths (loopback gate, required headers skip, user injection) are exercised with both positive and negative cases in the existing auth unit test suite.
  • Existing test that already covers this (if any): Existing trusted_proxy_loopback_source test retained — confirms default behavior unchanged.

User-visible / Behavior Changes

  • New optional config fields under gateway.auth.trustedProxy:
    • allowLoopback: boolean (default: false) — allow loopback addresses in trusted-proxy mode
    • loopbackUser: string (optional) — identity to assign to headerless loopback connections
  • No change to default behavior. Both fields are opt-in. Existing deployments are unaffected.

Diagram (if applicable)

Before:
[browser tool] -> ws://127.0.0.1:18789 -> trusted_proxy_loopback_source REJECT (hard, unconditional)

After (allowLoopback: true, loopbackUser: "agent@internal"):
[browser tool] -> ws://127.0.0.1:18789 -> loopback check PASS -> requiredHeaders SKIP -> loopbackUser injected -> authorized as agent@internal

After (allowLoopback: true, with proxy headers):
[oauth2-proxy] -> ws://127.0.0.1:18789 -> loopback check PASS -> requiredHeaders checked -> userHeader read -> authorized as real-user@example.com

Security Impact (required)

  • New permissions/capabilities? YesallowLoopback relaxes the loopback rejection for trusted-proxy mode
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: allowLoopback is opt-in (default false), so existing deployments are unaffected. When enabled, loopback connections are still subject to all other trusted-proxy checks (user header validation, allowUsers gating). loopbackUser only fires when the user header is absent AND the connection is from loopback AND allowLoopback is true — it cannot be triggered from non-loopback sources. Real proxy headers always take precedence over loopbackUser.

Repro + Verification

Environment

  • OS: Linux (EKS us-east-2, Azure AKS)
  • Runtime/container: ghcr.io/openclaw/openclaw:2026.4.2 in K8s StatefulSet
  • Model/provider: Any (kilocode/anthropic/claude-opus-4.6)
  • Integration/channel: Discord, Control UI via oauth2-proxy sidecar
  • Relevant config (redacted):
{
  gateway: {
    bind: "lan",
    trustedProxies: ["${POD_IP}/32"],
    auth: {
      mode: "trusted-proxy",
      trustedProxy: {
        userHeader: "X-Forwarded-Email",
        requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"],
        allowLoopback: true,
        loopbackUser: "agent@internal",
        allowUsers: ["mitch.rosmarin@codiac.io", "agent@internal"],
      },
    },
  },
}

Steps

  1. Deploy OpenClaw to K8s with auth.mode=trusted-proxy, bind=lan, oauth2-proxy sidecar
  2. Ask the agent to browse a URL (triggers browser tool snapshot)
  3. Check logs for trusted_proxy_loopback_source rejection

Expected

  • Browser tool snapshot succeeds; agent returns page content

Actual (before fix)

  • [ws] unauthorized conn=... remote=127.0.0.1 reason=trusted_proxy_loopback_source
  • [tools] browser failed: gateway closed (1008): unauthorized

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
# Before:
[ws] unauthorized conn=a66bb9d7 remote=127.0.0.1 client=agent backend v2026.4.2 reason=trusted_proxy_loopback_source
gateway connect failed: GatewayClientRequestError: unauthorized
[tools] browser failed: gateway closed (1008): unauthorized
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Bind: lan

# After (unit tests):
✓ allows loopback when allowLoopback is true
✓ injects loopbackUser when userHeader is missing on loopback
✓ rejects loopback without userHeader when loopbackUser is not set
✓ prefers real userHeader over loopbackUser on loopback
✓ rejects same-host proxy request with missing required header
✓ allows same-host proxy request when allowLoopback is true

Human Verification (required)

  • Verified scenarios: All 6 new test cases pass. Existing trusted-proxy tests unchanged and passing. pnpm vitest run src/gateway/auth.test.ts green.
  • Edge cases checked: loopbackUser without allowLoopback (rejected), allowLoopback without loopbackUser (requires real headers), real headers preferred over loopbackUser, IPv6 loopback (::1).
  • What you did NOT verify: Full E2E in K8s deployment (requires building a custom image with this patch). Browser tool snapshot E2E. Discord exec approvals flow.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — both new fields are optional with safe defaults (allowLoopback: false, loopbackUser: undefined). No behavior change without explicit opt-in.
  • Config/env changes? Yes — two new optional fields under gateway.auth.trustedProxy.
  • Migration needed? No — existing configs work unchanged.

Risks and Mitigations

  • Risk: Operator enables allowLoopback without loopbackUser and expects headerless internal connections to work — they'll get trusted_proxy_user_missing.
    • Mitigation: JSDoc on loopbackUser explains the dependency. Could add a startup warning when allowLoopback=true and loopbackUser is unset.
  • Risk: loopbackUser identity could be used to bypass allowUsers restrictions if the operator forgets to add it to the list.
    • Mitigation: allowUsers check runs after user resolution — if loopbackUser is not in allowUsers, the connection is still rejected.

penggaolai and others added 2 commits April 7, 2026 14:00
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)
  ...
@mrosmarin mrosmarin requested a review from a team as a code owner April 8, 2026 21:33
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S labels Apr 8, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 8, 2026

Greptile Summary

This PR adds two opt-in fields — allowLoopback and loopbackUser — to gateway.auth.trustedProxy so that same-pod internal connections (browser tool, sub-agents, CLI) are no longer hard-rejected by the loopback guard in trusted-proxy mode. Both new fields default to false/undefined so existing deployments are unaffected.

  • P1 — loopbackUser injection is blocked by requiredHeaders: In authorizeTrustedProxy, the requiredHeaders enforcement loop executes before the loopbackUser fallback. A headerless loopback connection (the primary use case) will fail with trusted_proxy_missing_header_<header> whenever requiredHeaders is non-empty — which is the recommended security config. The test at auth.test.ts:957 confirms this is the intended path: it spreads trustedProxyConfig (which has requiredHeaders: [\"x-forwarded-proto\"]) and sends no required headers, yet expects ok: true. That test will fail against the current implementation. The fix is to skip requiredHeaders for connections that will resolve via loopbackUser (i.e., loopback + allowLoopback + loopbackUser set).

Confidence Score: 4/5

Not safe to merge as-is: the loopbackUser injection is broken when requiredHeaders are configured, and the test that should catch this will also fail.

One P1 defect: requiredHeaders are enforced before the loopbackUser fallback fires, so the advertised primary use-case (headerless browser-tool connections with requiredHeaders set) silently fails at the required-header gate. The corresponding test (line 957) sends no required headers yet expects success — it will fail against the current implementation. Config types, zod schema, and the overall opt-in design are solid. Fixing the order of checks (skip requiredHeaders for loopback+loopbackUser connections) resolves both the runtime bug and the failing test.

src/gateway/auth.ts (requiredHeaders enforcement order) and src/gateway/auth.test.ts (test at line 957 is inconsistent with the implementation)

Vulnerabilities

No new security vulnerabilities identified. allowLoopback is opt-in (default false), so the loopback rejection that guards against header-forgery from same-host callers is unchanged unless the operator explicitly enables the flag. When allowLoopback: true, the allowUsers allowlist still runs after user resolution, limiting which identities can authenticate. loopbackUser injection fires only when the source address is loopback and allowLoopback is true, making it unreachable from external connections. Real proxy headers always take precedence over loopbackUser. No secrets, tokens, or network call surfaces changed.

Comments Outside Diff (1)

  1. src/gateway/auth.test.ts, line 1102-1122 (link)

    P2 Misleading test name and reason

    The test is named "rejects same-host proxy request with missing required header", but allowLoopback is false (it uses the bare trustedProxyConfig), so the rejection hits trusted_proxy_loopback_source — not a missing-header error. The test is actually verifying the default-closed loopback behavior, not the required-header path.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/gateway/auth.test.ts
    Line: 1102-1122
    
    Comment:
    **Misleading test name and reason**
    
    The test is named "rejects same-host proxy request with missing required header", but `allowLoopback` is `false` (it uses the bare `trustedProxyConfig`), so the rejection hits `trusted_proxy_loopback_source` — not a missing-header error. The test is actually verifying the default-closed loopback behavior, not the required-header path.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/auth.ts
Line: 396-411

Comment:
**`requiredHeaders` blocks `loopbackUser` injection**

The `requiredHeaders` loop runs before the `loopbackUser` fallback (line 409), so any request that lacks a required header fails with `trusted_proxy_missing_header_<header>` before the loopback identity injection is ever reached. The primary use-case — a browser-tool or sub-agent connecting from `127.0.0.1` with no proxy headers while `requiredHeaders: ["x-forwarded-proto"]` is set — will always fail on the header check rather than falling through to `loopbackUser`.

The PR description explicitly says "requiredHeaders SKIP" for the loopback+loopbackUser path, and the test at line 957 expects `res.ok === true` with `headers: { host: "localhost" }` while `trustedProxyConfig` contains `requiredHeaders: ["x-forwarded-proto"]` — that test will fail with `trusted_proxy_missing_header_x-forwarded-proto` against the current code.

```ts
// suggested fix: skip requiredHeaders for loopback+loopbackUser connections
const isLoopback = isLoopbackAddress(remoteAddr);
const isLoopbackInternal = isLoopback && trustedProxyConfig.allowLoopback && trustedProxyConfig.loopbackUser;

if (!isLoopbackInternal) {
  const requiredHeaders = trustedProxyConfig.requiredHeaders ?? [];
  for (const header of requiredHeaders) {
    const value = headerValue(req.headers[normalizeLowercaseStringOrEmpty(header)]);
    if (!value || value.trim() === "") {
      return { reason: `trusted_proxy_missing_header_${header}` };
    }
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/gateway/auth.test.ts
Line: 1102-1122

Comment:
**Misleading test name and reason**

The test is named "rejects same-host proxy request with missing required header", but `allowLoopback` is `false` (it uses the bare `trustedProxyConfig`), so the rejection hits `trusted_proxy_loopback_source` — not a missing-header error. The test is actually verifying the default-closed loopback behavior, not the required-header path.

```suggestion
    it("rejects same-host proxy request with default allowLoopback (loopback_source)", async () => {
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(gateway): add loopbackUser identity..." | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7614779cc8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/auth.ts Outdated
Comment on lines +409 to +410
if ((!userHeaderValue || userHeaderValue.trim() === "") && isLoopbackAddress(remoteAddr) && trustedProxyConfig.allowLoopback && trustedProxyConfig.loopbackUser) {
userHeaderValue = trustedProxyConfig.loopbackUser;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move loopbackUser fallback before required-header validation

With allowLoopback: true and loopbackUser configured, headerless loopback clients are still rejected whenever requiredHeaders is non-empty, because the requiredHeaders loop runs earlier and returns trusted_proxy_missing_header_* before this fallback can execute. In practice this keeps internal loopback callers (browser tool/sub-agents/CLI) broken in trusted-proxy setups that require headers like x-forwarded-proto.

Useful? React with 👍 / 👎.

Comment thread src/gateway/auth.ts
Comment on lines 396 to +411
@@ -399,9 +401,14 @@ function authorizeTrustedProxy(params: {
}
}

const userHeaderValue = headerValue(
let userHeaderValue = headerValue(
req.headers[normalizeLowercaseStringOrEmpty(trustedProxyConfig.userHeader)],
);
// Internal loopback connections (browser tool, sub-agents, CLI) don't carry
// proxy headers. Use the configured loopbackUser identity when available.
if ((!userHeaderValue || userHeaderValue.trim() === "") && isLoopbackAddress(remoteAddr) && trustedProxyConfig.allowLoopback && trustedProxyConfig.loopbackUser) {
userHeaderValue = trustedProxyConfig.loopbackUser;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 requiredHeaders blocks loopbackUser injection

The requiredHeaders loop runs before the loopbackUser fallback (line 409), so any request that lacks a required header fails with trusted_proxy_missing_header_<header> before the loopback identity injection is ever reached. The primary use-case — a browser-tool or sub-agent connecting from 127.0.0.1 with no proxy headers while requiredHeaders: ["x-forwarded-proto"] is set — will always fail on the header check rather than falling through to loopbackUser.

The PR description explicitly says "requiredHeaders SKIP" for the loopback+loopbackUser path, and the test at line 957 expects res.ok === true with headers: { host: "localhost" } while trustedProxyConfig contains requiredHeaders: ["x-forwarded-proto"] — that test will fail with trusted_proxy_missing_header_x-forwarded-proto against the current code.

// suggested fix: skip requiredHeaders for loopback+loopbackUser connections
const isLoopback = isLoopbackAddress(remoteAddr);
const isLoopbackInternal = isLoopback && trustedProxyConfig.allowLoopback && trustedProxyConfig.loopbackUser;

if (!isLoopbackInternal) {
  const requiredHeaders = trustedProxyConfig.requiredHeaders ?? [];
  for (const header of requiredHeaders) {
    const value = headerValue(req.headers[normalizeLowercaseStringOrEmpty(header)]);
    if (!value || value.trim() === "") {
      return { reason: `trusted_proxy_missing_header_${header}` };
    }
  }
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/auth.ts
Line: 396-411

Comment:
**`requiredHeaders` blocks `loopbackUser` injection**

The `requiredHeaders` loop runs before the `loopbackUser` fallback (line 409), so any request that lacks a required header fails with `trusted_proxy_missing_header_<header>` before the loopback identity injection is ever reached. The primary use-case — a browser-tool or sub-agent connecting from `127.0.0.1` with no proxy headers while `requiredHeaders: ["x-forwarded-proto"]` is set — will always fail on the header check rather than falling through to `loopbackUser`.

The PR description explicitly says "requiredHeaders SKIP" for the loopback+loopbackUser path, and the test at line 957 expects `res.ok === true` with `headers: { host: "localhost" }` while `trustedProxyConfig` contains `requiredHeaders: ["x-forwarded-proto"]` — that test will fail with `trusted_proxy_missing_header_x-forwarded-proto` against the current code.

```ts
// suggested fix: skip requiredHeaders for loopback+loopbackUser connections
const isLoopback = isLoopbackAddress(remoteAddr);
const isLoopbackInternal = isLoopback && trustedProxyConfig.allowLoopback && trustedProxyConfig.loopbackUser;

if (!isLoopbackInternal) {
  const requiredHeaders = trustedProxyConfig.requiredHeaders ?? [];
  for (const header of requiredHeaders) {
    const value = headerValue(req.headers[normalizeLowercaseStringOrEmpty(header)]);
    if (!value || value.trim() === "") {
      return { reason: `trusted_proxy_missing_header_${header}` };
    }
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

…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
@mrosmarin mrosmarin force-pushed the feature/trusted-proxy-loopback branch from 7614779 to d73d8e5 Compare April 8, 2026 22:19
@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: M and removed size: S labels Apr 8, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d73d8e5655

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/auth.ts
Comment on lines +568 to +572
const isInternalBackend =
(params.clientMode === GATEWAY_CLIENT_MODES.BACKEND ||
params.clientMode === GATEWAY_CLIENT_MODES.PROBE) &&
remoteAddr != null &&
isLoopbackAddress(remoteAddr);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep backend/probe loopback on trusted-proxy path

This new isInternalBackend gate skips authorizeTrustedProxy() for loopback clients in BACKEND/PROBE mode, but execution then stays in auth.mode === "trusted-proxy" and eventually returns generic unauthorized (token/password branches are never reachable in this mode). Internal GatewayClient callers default to BACKEND mode (src/gateway/call.ts), so the advertised allowLoopback/loopbackUser fix still rejects browser/sub-agent/probe self-connections in production trusted-proxy deployments.

Useful? React with 👍 / 👎.

Comment thread src/gateway/auth.ts
Comment on lines +406 to +409
const isInternalLoopback =
isLoopbackAddress(remoteAddr) &&
trustedProxyConfig.allowLoopback &&
trustedProxyConfig.loopbackUser;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict required-header bypass to true loopback fallback

isInternalLoopback is true for any loopback request whenever allowLoopback and loopbackUser are set, so requiredHeaders are skipped even when a real proxied userHeader is present. In same-host sidecar setups this effectively disables required-header enforcement for normal proxied traffic, weakening the trusted-proxy hardening and allowing missing-header requests to authenticate if they include a user header.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cf6efce4fc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/auth.ts
trustedProxyConfig.loopbackUser;
if (!isInternalLoopback) {
for (const h of requiredHeaders) {
if (!headerValue(req.headers[normalizeLowercaseStringOrEmpty(h)])) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject whitespace-only trusted-proxy required headers

The new required-header check now only verifies header presence (headerValue(...)) and no longer trims values before validation, so a header like x-forwarded-proto: " " is treated as valid. In trusted-proxy mode this weakens the requiredHeaders hardening and can allow requests to pass provenance checks with effectively empty forwarded metadata as long as a user header is present.

Useful? React with 👍 / 👎.

Comment thread src/gateway/call.ts
Comment on lines +173 to +177
if (bindMode === "lan" || bindMode === "custom") {
// In K8s/container environments prefer POD_IP when available.
const podIp = process.env["POD_IP"];
if (podIp) {
return `${scheme}://${podIp}:${port}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor customBindHost in internal gateway URL resolution

resolveInternalGatewayUrl() groups bind === "custom" with LAN resolution and never reads gateway.customBindHost, so it can return POD_IP or the primary LAN IP even when the gateway is bound to a different custom address. Any caller that follows the new deprecation guidance and uses this helper can fail to connect in custom-bind deployments (for example when customBindHost is 127.0.0.1 or another non-primary interface).

Useful? React with 👍 / 👎.

@mrosmarin mrosmarin force-pushed the feature/trusted-proxy-loopback branch from cf6efce to 12e0bf9 Compare April 10, 2026 13:14
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12e0bf997c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/gateway/call.ts
Comment on lines +173 to +177
if (bindMode === "lan" || bindMode === "custom") {
// In K8s/container environments prefer POD_IP when available.
const podIp = process.env["POD_IP"];
if (podIp) {
return `${scheme}://${podIp}:${port}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return loopback URL when local TLS is disabled

resolveInternalGatewayUrl() now returns ws://<POD_IP|LAN_IP> for bind: "lan"|"custom", but GatewayClient rejects plaintext ws:// to non-loopback hosts by default (src/gateway/client.ts security check), so callers that follow the new deprecation guidance and pass this helper result into callGateway/callGatewayTool will fail before auth unless they set the insecure override env var. Keep loopback for non-TLS local URLs (or otherwise ensure a secure URL path) so the new helper is usable in default local-trust deployments.

Useful? React with 👍 / 👎.

Comment thread src/gateway/auth.ts
Comment on lines +412 to +413
if (!headerValue(req.headers[normalizeLowercaseStringOrEmpty(h)])) {
return { reason: "trusted_proxy_required_header_missing" };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve per-header trusted-proxy failure reason

The required-header loop now emits the generic trusted_proxy_required_header_missing reason instead of trusted_proxy_missing_header_<header>, which breaks existing expectations in this repo (for example src/gateway/auth.test.ts still asserts the header-specific reason) and removes actionable detail from failures. If this reason shape is being changed intentionally, all in-repo consumers/tests should be updated in the same change; otherwise keep the previous per-header reason format.

Useful? React with 👍 / 👎.

@vincentkoc
Copy link
Copy Markdown
Member

Thanks @mrosmarin. I landed the narrower same-host reverse-proxy part in #73266: gateway.auth.trustedProxy.allowLoopback now allows loopback trusted-proxy sources only after explicit opt-in, while preserving requiredHeaders and allowUsers checks.

I am closing this PR as superseded because the broader loopbackUser / internal-service identity shape is still better tracked separately by #43300 and #69066.

@vincentkoc vincentkoc closed this Apr 28, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling gateway Gateway runtime size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Feature request: trustedProxy.loopbackUser for CLI/sub-agent access without proxy

3 participants