Skip to content

Pr 33819 fallback fixes#54718

Open
rickstonerz wants to merge 7 commits intoopenclaw:mainfrom
rickstonerz:pr-33819-fallback-fixes
Open

Pr 33819 fallback fixes#54718
rickstonerz wants to merge 7 commits intoopenclaw:mainfrom
rickstonerz:pr-33819-fallback-fixes

Conversation

@rickstonerz
Copy link
Copy Markdown

PR Fallback Fixes

This note exists as a clean fallback summary for the branch and PR flow when the GitHub compare page is flaky or the web UI does not render the diff reliably.

Branch Context

  • Base repository: openclaw/openclaw
  • Base branch: main
  • Head repository: rickstonerz/openclawshlt
  • Head branch: pr-33819-fallback-fixes

What This Branch Contains

This branch carries the trusted-proxy local fallback fixes for gateway authentication behavior.

Included commits:

  • 3e739d4 fix(gateway): honor configured trusted-proxy headers in local fallback
  • 15a4fb4 fix(gateway): tighten trusted-proxy local fallback
  • 3ee7c48 fix(gateway): allow local shared-secret auth in trusted-proxy mode

If The Compare Page Misbehaves

If GitHub fails to render the comparison cleanly, verify the branch locally with:

git fetch origin
git fetch rickstonerz pr-33819-fallback-fixes || true
git diff main...pr-33819-fallback-fixes
git log --oneline --decorate main..pr-33819-fallback-fixes

If the compare URL still shows the correct base/head branches and commit list, the PR can still be created even if the UI reports rendering errors.

Purpose Of This Fallback Note

This is here to preserve the exact branch/commit context so the PR can be described accurately without relying on a flaky compare page.

@rickstonerz rickstonerz requested a review from a team as a code owner March 25, 2026 21:49
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S labels Mar 25, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 25, 2026

Greptile Summary

This PR introduces a local-loopback shared-secret fallback for trusted-proxy auth mode. When the gateway receives a connection whose socket originates from a loopback address (127.0.0.1 / ::1) and that request does not carry any of the operator-configured trusted-proxy headers, the gateway now permits authentication via a configured token or password — the same credential types used in standard token/password modes. If the proxy headers are present, the new path is completely bypassed and the existing trusted-proxy flow runs unchanged.

Key changes:

  • New authorizeSharedSecretFallback helper encapsulates the token/password check with limiter integration, making the logic reusable and self-contained.
  • New hasConfiguredTrustedProxyHeaders helper correctly scopes the "has proxy headers" check to only the headers the operator actually configured (userHeader + requiredHeaders), so generic headers like x-forwarded-for don't suppress the fallback.
  • limiter/ip/rateLimitScope variable declarations are correctly hoisted above the trusted-proxy branch so they are available for the new rate-limit guard.
  • Rate-limit enforcement for the fallback path mirrors the existing pattern for token/password modes: check before attempting auth, record failure on mismatch, reset on success.
  • Three new tests cover the happy-path local token auth, proxy-header auth preempting a wrong local token, and rate-limiting blocking the fallback.

One subtlety worth being aware of: when localLoopbackWithoutProxyHeaders is true but authorizeSharedSecretFallback returns null (no credentials provided at all), the request falls through into the standard trusted-proxy flow and will receive a proxy-related error reason (e.g. trusted_proxy_user_missing) rather than a no_credentials message. This is not a bug — the operator configured trusted-proxy mode — but it could be slightly surprising during debugging.

Confidence Score: 5/5

  • This PR is safe to merge — the fallback is tightly scoped to loopback-only connections without proxy headers, rate-limiting is properly wired, and three targeted tests validate the key scenarios.
  • No critical bugs found. The logic correctly gates the shared-secret fallback on both a loopback socket address AND the absence of configured proxy headers, preventing any weakening of the trusted-proxy security model for non-local requests. The helper functions are clean, the variable hoisting is correct, and the test coverage is appropriate for the new paths.
  • No files require special attention.

Reviews (1): Last reviewed commit: "fix(gateway): honor configured trusted-p..." | 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: 3e739d4c75

ℹ️ 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 +379 to +383
if (auth.password && connectAuth?.password) {
if (!safeEqualSecret(connectAuth.password, auth.password)) {
limiter?.recordFailure(ip, rateLimitScope);
return { ok: false, reason: "password_mismatch" };
}
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 Check token before password in trusted-proxy fallback

In authorizeSharedSecretFallback, the password branch runs first and returns password_mismatch immediately, so the token branch is never reached when both shared secrets are configured. This breaks local trusted-proxy fallback for normal bearer-token HTTP requests, because the HTTP entry points pass connectAuth as { token, password: token } (for example in src/gateway/server-http.ts and src/gateway/http-auth-helpers.ts), which means a valid token is rejected whenever auth.password is set and differs from the token.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. I updated the trusted-proxy shared-secret fallback to prefer token auth before password auth so bearer-token HTTP requests are not rejected when both shared secrets are configured, and added targeted coverage for that path.

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: f1846c7d9b

ℹ️ 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 +438 to +441
const localLoopbackWithoutProxyHeaders =
Boolean(req) &&
isLoopbackAddress(req?.socket?.remoteAddress) &&
!hasConfiguredTrustedProxyHeaders(req, auth.trustedProxy);
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 Restrict fallback to true local-direct trusted-proxy requests

The localLoopbackWithoutProxyHeaders gate only checks whether req.socket.remoteAddress is loopback and configured proxy headers are absent, so requests forwarded through a same-host reverse proxy (remote 127.0.0.1) can still enter shared-secret fallback even when the actual client is external (for example via x-forwarded-for). In trusted-proxy mode this widens auth from “proxy-vouched identity only” to bearer token/password for proxied external traffic whenever shared secrets are configured, which can bypass intended trusted-proxy enforcement; this branch should be gated by proxy-aware local-direct detection (for example isLocalDirectRequest) rather than raw socket loopback.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. I tightened the fallback gate to require a true local-direct request rather than raw loopback socket detection, so same-host proxied external traffic no longer qualifies for shared-secret fallback. I also added regression coverage for that proxied-external case.

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: b280776e68

ℹ️ 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 429 to 430
trustedProxies,
params.allowRealIpFallback === true,
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 Use proxy-agnostic local detection before trusted-proxy fallback

localDirect is computed via isLocalDirectRequest, which in turn relies on resolveRequestClientIp; that helper intentionally returns undefined when the remote is in trustedProxies and forwarding headers are absent. In the common bind=loopback trusted-proxy setup, src/gateway/server-runtime-config.ts requires loopback entries in gateway.trustedProxies, so direct loopback requests hit this fail-closed path and localDirect becomes false, meaning the new shared-secret fallback branch is never reached. This makes the advertised local-direct token/password fallback unavailable in the primary same-host proxy deployment and requests still fail with trusted-proxy header errors.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. The previous gate reused isLocalDirectRequest too literally for the loopback trusted-proxy case, and that helper fails closed when a trusted proxy is configured without forwarding headers. I updated it so true same-host direct loopback requests with no forwarding headers still count as local-direct, while proxied external traffic with forwarding headers still does not qualify for shared-secret fallback.

@rickstonerz rickstonerz force-pushed the pr-33819-fallback-fixes branch from b280776 to 78378d2 Compare March 26, 2026 03:32
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: 78378d2e51

ℹ️ 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 +138 to +139
if (isLocalishHost(req.headers?.host) && isLoopbackAddress(remoteAddr) && !hasForwarded) {
return true;
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 Reject loopback requests carrying proxy-forwarded context

This early return marks a request as localDirect before proxy-aware IP resolution whenever Host is localish and the socket is loopback, but hasForwarded only checks x-forwarded-for/x-real-ip/x-forwarded-host. A same-host reverse proxy that forwards external traffic with other headers (for example x-forwarded-proto plus identity headers, or rewritten loopback Host) can therefore be misclassified as local, which then changes downstream security decisions (e.g. auth bypass path in authorizeGatewayConnect and local-client behavior used by WebSocket/origin/pairing flows). The fresh evidence is that this new short-circuit at src/gateway/auth.ts:138-139 bypasses resolveRequestClientIp entirely for those requests.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. I reverted the broad isLocalDirectRequest short-circuit and moved the special handling into a trusted-proxy-specific fallback gate instead. The fallback now only applies to true same-host loopback requests with a localish Host and no forwarding/proxy context headers or configured trusted-proxy headers, and I added regression coverage for loopback requests carrying proxy-forwarding context.

@rickstonerz rickstonerz force-pushed the pr-33819-fallback-fixes branch 2 times, most recently from 24fc413 to 33f3b46 Compare March 26, 2026 03:44
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: 33f3b469c0

ℹ️ 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 +455 to +458
const limiter = params.rateLimiter;
const ip =
params.clientIp ??
resolveRequestClientIp(req, trustedProxies, params.allowRealIpFallback === true) ??
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 Reintroduce localDirect before tailscale auth gate

This refactor removed the localDirect = isLocalDirectRequest(...) initialization, but the later gate still evaluates !localDirect in authorizeGatewayConnect. On ws-control-ui requests where auth.allowTailscale is true, that expression throws at runtime before token/password auth runs, so control-UI authentication can fail with a server error instead of returning an auth result.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I restored the missing initialization and verified the control-UI path no longer throws before normal auth evaluation. The current branch now keeps the intended behavior:

  • local direct requests continue through normal token/password auth
  • trusted-proxy logic stays scoped to the proxy-aware path
  • control-UI requests with auth.allowTailscale no longer fail with a server error from an uninitialized localDirect reference

@rickstonerz rickstonerz force-pushed the pr-33819-fallback-fixes branch from 33f3b46 to f7558d3 Compare March 26, 2026 03:53
@rickstonerz
Copy link
Copy Markdown
Author

For clarity: this is the canonical PR for the trusted-proxy fallback/header-handling fix originally proposed in #33819.

#33819 contained the earlier iteration, but this branch is the narrowed current-base version intended for review and merge.

The authored fix sequence remains:

  • allow local shared-secret auth in trusted-proxy mode
  • tighten trusted-proxy local fallback
  • honor configured trusted-proxy headers in local fallback

@rickstonerz
Copy link
Copy Markdown
Author

Hey @huntharo — friendly ping! PR #54718 is ready and working — 30/30 checks passing, compiles cleanly on macOS. Would love a review when you get a chance.

@rickstonerz
Copy link
Copy Markdown
Author

Updating this PR with current status and pushing back on the "superseded" framing.

What actually happened:

PR #54536 ("fix(gateway/auth): local trusted-proxy fallback to require token auth") was merged into main. That fix is a direct subset of what this PR proposes. The core idea — that same-host loopback requests in trusted-proxy mode should be able to fall back to token/shared-secret auth — was validated and merged. This PR got the concept right first.

On the RFC #69066 "superseded" claim:

The RFC lists this PR as something Phase 2 would supersede. But the RFC's own Phase 0 recommendation was to merge the two most-validated in-flight PRs as "complementary opt-ins" — and one of those was effectively this approach. The direction this PR takes (token fallback for loopback trusted-proxy) is exactly what #54536 shipped.

Current state:

  • 30/30 checks passing on last reviewed commit
  • Greptile gave 5/5 confidence: "safe to merge, no critical bugs found"
  • Conflicts exist with main due to 4.23 auth.ts changes — I will rebase

What I'm asking:

Before this gets closed as "superseded by RFC Phase 2", please consider that:

  1. The RFC Phase 1 hasn't shipped yet
  2. Users hitting trusted_proxy_loopback_source today have no fix in current main
  3. This PR covers the remaining cases fix(gateway/auth): local trusted-proxy fallback to require token auth #54536 didn't fully address

Happy to rebase and address any remaining review feedback. The real-world deployment case (nginx same-host reverse proxy + token auth + internal TLS) is documented and working — this PR makes it work without config gymnastics.

cc @vincentkoc @huntharo

@rickstonerz rickstonerz force-pushed the pr-33819-fallback-fixes branch from 2e94067 to 4c4346a Compare April 24, 2026 21:24
nginx-on-the-same-box reverse proxies arrive with socket.remoteAddress 127.0.0.1
but carry a non-local Host header and standard forwarding headers. The previous
loopback guard in authorizeTrustedProxy rejected all loopback sources unconditionally,
breaking this legitimate same-host proxy pattern.

Fix: permit loopback addresses through the trusted-proxy header-validation path
when the request appears proxied (non-local-ish Host + forwarding context present).
Plain loopback connections and requests that spoof a non-local Host without
forwarding context are still rejected as trusted_proxy_loopback_source.

The token fallback for direct node connections (isLocalDirectRequest path) is
unchanged — it operates before the trusted-proxy path and handles the openclaw-node
use-case where a node connects directly to the gateway on the same host.

Update tests to reflect correct behavior:
- loopback + non-local host + forwarding headers => trusted-proxy auth succeeds
- loopback + non-local host + forwarding headers + missing user => trusted_proxy_user_missing
- loopback + local host + any forwarding context => trusted_proxy_loopback_source
- direct loopback + valid token => ok (token method)
- direct loopback + wrong token => token_mismatch (not loopback_source)
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 30, 2026

Codex review: found issues before merge.

Summary
The PR changes gateway trusted-proxy auth to add local shared-secret/token fallback behavior and same-host loopback proxy handling, with expanded gateway auth tests.

Reproducibility: yes. The PR diff and current main tests provide source-level reproductions for token fallback, loopback proxy headers, local-direct detection, and config-guard ordering; I did not run tests because this was a required read-only review.

Next step before merge
Maintainers/security owners need to choose the gateway auth direction before any repair; the blockers are trust-boundary and product-contract decisions, not a narrow mechanical fix.

Security
Needs attention: The diff changes gateway authentication trust boundaries and introduces concrete loopback trusted-proxy and token-mode concerns.

Review findings

  • [P1] Require explicit opt-in for loopback proxy headers — src/gateway/auth.ts:301-302
  • [P2] Preserve raw loopback local-direct detection — src/gateway/auth.ts:151-153
  • [P2] Align token fallback with startup validation — src/gateway/auth.ts:390-396
Review details

Best possible solution:

Keep the explicit current model unless maintainers revise it: password-only local-direct fallback, trustedProxy.allowLoopback for same-host reverse proxies, and any token/service identity work through the RFC with coordinated config, docs, and tests.

Do we have a high-confidence way to reproduce the issue?

Yes. The PR diff and current main tests provide source-level reproductions for token fallback, loopback proxy headers, local-direct detection, and config-guard ordering; I did not run tests because this was a required read-only review.

Is this the best way to solve the issue?

No. The PR is broader than the maintained contract, and the safer path is to preserve explicit loopback opt-in and password-only fallback unless maintainers approve a service-identity/token design.

Full review comments:

  • [P1] Require explicit opt-in for loopback proxy headers — src/gateway/auth.ts:301-302
    This exception accepts trusted-proxy identity from a loopback source based on Host and forwarding-header shape instead of trustedProxy.allowLoopback. Local processes can forge those inputs, so same-host loopback trusted-proxy auth becomes implicit rather than explicitly configured.
    Confidence: 0.9
  • [P2] Preserve raw loopback local-direct detection — src/gateway/auth.ts:151-153
    isLocalDirectRequest now depends on proxy-aware client IP resolution. When loopback is listed in gateway.trustedProxies and no forwarded client header is present, that resolver returns no client IP, so direct loopback requests may stop being classified as local-direct for other gateway paths.
    Confidence: 0.86
  • [P2] Align token fallback with startup validation — src/gateway/auth.ts:390-396
    The fallback accepts auth.token in trusted-proxy mode, but current startup validation rejects trusted-proxy configs that also set a shared token. The advertised token fallback is therefore not usable through normal config unless validation and docs change with it.
    Confidence: 0.88
  • [P2] Keep trusted-proxy config guards before fallback — src/gateway/auth.ts:528-537
    The local shared-secret fallback runs before the missing trustedProxy and empty trustedProxies checks. That lets a malformed trusted-proxy runtime auth object authenticate on loopback by shared secret instead of failing closed like current main.
    Confidence: 0.78

Overall correctness: patch is incorrect
Overall confidence: 0.9

Security concerns:

  • [high] Implicit loopback trusted-proxy trust — src/gateway/auth.ts:301
    The PR would allow trusted-proxy identity headers from 127.0.0.1 based on Host and forwarding-header shape instead of requiring trustedProxy.allowLoopback; local processes can forge those inputs, weakening the explicit same-host proxy trust boundary.
    Confidence: 0.9
  • [medium] Trusted-proxy token fallback broadens auth mode — src/gateway/auth.ts:390
    The PR adds token fallback inside trusted-proxy mode while current main treats token and trusted-proxy as mutually exclusive. That changes credential eligibility for a security-sensitive auth mode without the matching startup/docs contract.
    Confidence: 0.84
  • [medium] Malformed trusted-proxy configs can authenticate by fallback — src/gateway/auth.ts:528
    Because fallback is evaluated before trusted-proxy config and proxy-list guards, direct callers can authenticate with a shared secret even when the trusted-proxy mode lacks the proxy contract that current main requires before any fallback.
    Confidence: 0.78

Acceptance criteria:

  • pnpm test src/gateway/auth.test.ts
  • pnpm test src/gateway/server/ws-connection/connect-policy.test.ts
  • pnpm test src/gateway/server/ws-connection/auth-context.test.ts
  • pnpm exec oxfmt --check --threads=1 src/gateway/auth.ts src/gateway/auth.test.ts docs/gateway/trusted-proxy-auth.md

What I checked:

  • PR head permits implicit loopback proxy trust: At the PR head, loopback trusted-proxy auth is allowed when Host is non-localish and any forwarding context is present, without requiring trustedProxy.allowLoopback. (src/gateway/auth.ts:301, d6a8962028dc)
  • PR head accepts token fallback in trusted-proxy mode: The PR's fallback helper accepts auth.token inside trusted-proxy mode and returns token auth on match. (src/gateway/auth.ts:390, d6a8962028dc)
  • PR head runs fallback before trusted-proxy guards: The PR checks and returns shared-secret fallback before the missing trustedProxy and empty trustedProxies fail-closed checks. (src/gateway/auth.ts:528, d6a8962028dc)
  • Current main rejects mixed trusted-proxy token config: Current startup validation rejects trusted-proxy mode when a shared token is configured, preserving token/trusted-proxy mutual exclusivity. (src/gateway/auth.ts:245, 14b5f73e2a5e)
  • Current main keeps explicit loopback and password-only fallback contract: Current main checks trustedProxy config and trustedProxies before auth fallback, rejects loopback proxy identity unless allowLoopback is set, and only allows password fallback for local-direct trusted-proxy failures. (src/gateway/auth.ts:442, 14b5f73e2a5e)
  • Current docs document the maintained auth boundary: Trusted-proxy docs say same-host loopback reverse proxies require explicit allowLoopback, internal direct callers should use gateway.auth.password, and token fallback remains intentionally unsupported. Public docs: docs/gateway/trusted-proxy-auth.md. (docs/gateway/trusted-proxy-auth.md:96, 14b5f73e2a5e)

Likely related people:

Remaining risk / open question:

Codex review notes: model gpt-5.5, reasoning high; reviewed against 14b5f73e2a5e.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant