Skip to content

fix: allow trusted-proxy local password fallback#73034

Merged
steipete merged 2 commits intomainfrom
fix/trusted-proxy-password-fallback
Apr 27, 2026
Merged

fix: allow trusted-proxy local password fallback#73034
steipete merged 2 commits intomainfrom
fix/trusted-proxy-password-fallback

Conversation

@steipete
Copy link
Copy Markdown
Contributor

Summary

  • allow same-host direct callers in trusted-proxy mode to authenticate with the configured gateway password after proxy identity auth fails
  • keep trusted-proxy + token rejected, and keep password fallback unavailable to remote/direct callers
  • teach credential planning/secret input resolution/docs about the local password fallback

Closes #17761.
Supersedes the narrow trusted-proxy password-fallback slice of #64122 with rate-limit coverage and without the broader unrelated changes.

Tests

  • pnpm test src/gateway/auth.test.ts src/gateway/call.test.ts src/gateway/credentials.test.ts
  • pnpm check:changed

@steipete steipete requested a review from a team as a code owner April 27, 2026 20:49
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime labels Apr 27, 2026
@cursor
Copy link
Copy Markdown

cursor Bot commented Apr 27, 2026

PR Summary

Medium Risk
Touches gateway authentication and credential-selection logic; while scoped to loopback local-direct requests, mistakes could weaken security if the gateway port is reachable outside the proxy/firewall boundary.

Overview
Enables local-direct (loopback) callers to authenticate in gateway.auth.mode: "trusted-proxy" using gateway.auth.password when trusted-proxy identity checks fail, with rate-limit checks applied before accepting/rejecting the password.

Updates credential planning/SecretRef resolution so gateway.auth.password can be active and resolvable in trusted-proxy mode, while preventing gateway.remote.password from being used as a trusted-proxy local fallback and keeping trusted-proxy + token configurations rejected. Documentation and tests are updated to reflect the new fallback behavior and its security expectations.

Reviewed by Cursor Bugbot for commit df2428e. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR introduces a local password fallback for trusted-proxy mode: when proxy identity auth fails, same-host (loopback/local-direct) callers can now authenticate with gateway.auth.password, while token fallback and password fallback for non-local callers remain rejected. The change is propagated consistently through the credential planner, secret-input resolver, and credential resolution, with rate-limit integration and supporting tests.

Confidence Score: 4/5

Safe to merge; the fallback is correctly scoped to local-direct callers and guarded by rate limiting.

No P0 or P1 issues found. Two P2 style observations: the outer guard makes the password_missing_config/password_missing branches inside authorizePasswordAuth unreachable from the trusted-proxy path (intentional, but undocumented in code), and probing with no password credential bypasses rate-limit counting (consistent with the normal password-mode behaviour). All other changes are logically consistent and well-covered.

src/gateway/auth.ts around the trusted-proxy password fallback block (lines 464-484)

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/auth.ts
Line: 464-483

Comment:
**Outer guard makes `authorizePasswordAuth` dead paths unreachable**

The outer condition `localDirect && auth.password && connectAuth?.password` guards the block, so `authorizePasswordAuth` is always called with non-nullish `authPassword` and `connectPassword`. Its internal `password_missing_config` and `password_missing` branches can never fire from this path — the code falls through to `result.reason` instead. This is likely intentional information-hiding (don't reveal whether a password is configured on proxy failure), but the divergence from the normal `auth.mode === "password"` flow is worth a brief comment to make the intent explicit.

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.ts
Line: 464

Comment:
**Rate limiter not checked when `connectAuth?.password` is absent**

When a local-direct caller supplies no password at all (`connectAuth?.password` is falsy), the condition short-circuits and we return `result.reason` without ever touching the rate limiter. An attacker that repeatedly probes with no password credential can avoid incrementing the rate limit counter indefinitely. The normal `auth.mode === "password"` path has a similar property (returns `password_missing` without recording failure), so this is consistent — but it means the rate limiter only protects against wrong-password attacks, not credential-probing without a credential. Worth a comment acknowledging the deliberate choice.

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

Reviews (1): Last reviewed commit: "fix: allow trusted-proxy local password ..." | Re-trigger Greptile

Comment thread src/gateway/auth.ts
Comment on lines +464 to +483
if (localDirect && auth.password && connectAuth?.password) {
if (limiter) {
const rlCheck: RateLimitCheckResult = limiter.check(ip, rateLimitScope);
if (!rlCheck.allowed) {
return {
ok: false,
reason: "rate_limited",
rateLimited: true,
retryAfterMs: rlCheck.retryAfterMs,
};
}
}
return authorizePasswordAuth({
authPassword: auth.password,
connectPassword: connectAuth.password,
limiter,
ip,
rateLimitScope,
});
}
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.

P2 Outer guard makes authorizePasswordAuth dead paths unreachable

The outer condition localDirect && auth.password && connectAuth?.password guards the block, so authorizePasswordAuth is always called with non-nullish authPassword and connectPassword. Its internal password_missing_config and password_missing branches can never fire from this path — the code falls through to result.reason instead. This is likely intentional information-hiding (don't reveal whether a password is configured on proxy failure), but the divergence from the normal auth.mode === "password" flow is worth a brief comment to make the intent explicit.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/auth.ts
Line: 464-483

Comment:
**Outer guard makes `authorizePasswordAuth` dead paths unreachable**

The outer condition `localDirect && auth.password && connectAuth?.password` guards the block, so `authorizePasswordAuth` is always called with non-nullish `authPassword` and `connectPassword`. Its internal `password_missing_config` and `password_missing` branches can never fire from this path — the code falls through to `result.reason` instead. This is likely intentional information-hiding (don't reveal whether a password is configured on proxy failure), but the divergence from the normal `auth.mode === "password"` flow is worth a brief comment to make the intent explicit.

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

Comment thread src/gateway/auth.ts
@@ -439,6 +461,26 @@ async function authorizeGatewayConnectCore(
}
return { ok: true, method: "trusted-proxy", user: result.user };
}
if (localDirect && auth.password && connectAuth?.password) {
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.

P2 Rate limiter not checked when connectAuth?.password is absent

When a local-direct caller supplies no password at all (connectAuth?.password is falsy), the condition short-circuits and we return result.reason without ever touching the rate limiter. An attacker that repeatedly probes with no password credential can avoid incrementing the rate limit counter indefinitely. The normal auth.mode === "password" path has a similar property (returns password_missing without recording failure), so this is consistent — but it means the rate limiter only protects against wrong-password attacks, not credential-probing without a credential. Worth a comment acknowledging the deliberate choice.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/auth.ts
Line: 464

Comment:
**Rate limiter not checked when `connectAuth?.password` is absent**

When a local-direct caller supplies no password at all (`connectAuth?.password` is falsy), the condition short-circuits and we return `result.reason` without ever touching the rate limiter. An attacker that repeatedly probes with no password credential can avoid incrementing the rate limit counter indefinitely. The normal `auth.mode === "password"` path has a similar property (returns `password_missing` without recording failure), so this is consistent — but it means the rate limiter only protects against wrong-password attacks, not credential-probing without a credential. Worth a comment acknowledging the deliberate choice.

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

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: 2000897cee

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +118 to +119
if (authMode === "trusted-proxy") {
return !isTokenGatewaySecretInputPath(path);
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 Exclude remote password refs from trusted-proxy local auth

In local gateway.auth.mode: "trusted-proxy", this branch now treats every non-token secret path as eligible, which includes gateway.remote.password. Because gatewaySecretInputPathCanWin scans all gateway secret paths, a resolvable remote password ref can be selected and forwarded as the local shared secret, even though the fallback is documented/implemented as gateway.auth.password-based. This can make local direct auth use the wrong secret (and hit shared-secret rate limiting) when remote credentials differ from local trusted-proxy fallback credentials.

Useful? React with 👍 / 👎.

@steipete steipete force-pushed the fix/trusted-proxy-password-fallback branch from b90447e to 3133dee Compare April 27, 2026 21:08
@steipete steipete force-pushed the fix/trusted-proxy-password-fallback branch from 9f47678 to 861d2b2 Compare April 27, 2026 21:27
@openclaw-barnacle openclaw-barnacle Bot added extensions: memory-core Extension: memory-core and removed channel: discord Channel integration: discord extensions: memory-wiki size: M labels Apr 27, 2026
@steipete steipete force-pushed the fix/trusted-proxy-password-fallback branch from 8b63c60 to df2428e Compare April 27, 2026 22:07
@openclaw-barnacle openclaw-barnacle Bot added size: M and removed extensions: memory-core Extension: memory-core size: L labels Apr 27, 2026
@steipete steipete merged commit 7975305 into main Apr 27, 2026
68 of 69 checks passed
@steipete steipete deleted the fix/trusted-proxy-password-fallback branch April 27, 2026 22:10
@steipete
Copy link
Copy Markdown
Contributor Author

Landed via rebase onto main.

  • Source head: df2428e
  • Merge commit: 7975305
  • Local gates: pnpm test src/secrets/runtime-gateway-local-surfaces.test.ts src/secrets/runtime-gateway-auth-surfaces.test.ts src/gateway/credentials.test.ts src/gateway/call.test.ts src/gateway/auth.test.ts; pnpm check:changed
  • CI: core and extension aggregate checks green on the merged SHA

Thanks @steipete.

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

Labels

docs Improvements or additions to documentation gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Gateway auth dispatcher blocks all internal services when mode=trusted-proxy (no shared-secret fallback)

1 participant