fix(gateway): keep local CLI shared auth off device scopes#96002
Conversation
|
Codex review: found issues before merge. Reviewed July 5, 2026, 9:48 AM ET / 13:48 UTC. Summary PR surface: Source +35, Tests +141, Docs +1. Total +177 across 8 files. Reproducibility: yes. at source level. Explicit URL/password and trusted-proxy fallback password calls can send a password while the omission predicate sees a non-password auth mode, and direct_local scope preservation follows loopback socket classification without Host proof. Review metrics: 1 noteworthy metric.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review findings
Review detailsBest possible solution: Keep the fix in the Gateway client/server auth boundary, but derive device-identity omission from the credential actually sent and preserve CLI scopes only for shared-secret requests with local/private Host proof plus regressions for password fallback and public-Host loopback. Do we have a high-confidence way to reproduce the issue? Yes at source level. Explicit URL/password and trusted-proxy fallback password calls can send a password while the omission predicate sees a non-password auth mode, and direct_local scope preservation follows loopback socket classification without Host proof. Is this the best way to solve the issue? No. The Gateway client/server auth boundary is the right layer, but the current predicates are not the narrowest maintainable fix until credential-kind and locality proof are tightened. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against ba821e7220ff. Label changesLabel justifications:
Evidence reviewedPR surface: Source +35, Tests +141, Docs +1. Total +177 across 8 files. View PR surface stats
Security concerns:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (5 earlier review cycles)
|
40a4228 to
ef1a26e
Compare
|
Replayed and tightened on current Review found and fixed one regression in the submitted patch: inactive ambient credentials under Security boundary checked end to end:
Proof on Blacksmith Testbox
Review artifacts validate with zero findings. Exact-head hosted CI is the remaining merge gate; I will merge only after it is green. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ef1a26e63d
ℹ️ 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".
| const hasSharedSecretAuth = | ||
| (params.authMode === "token" && Boolean(params.token)) || | ||
| (params.authMode === "password" && Boolean(params.password)); |
There was a problem hiding this comment.
Honor explicit password auth when omitting device identity
When a caller supplies an explicit loopback url plus password, resolveGatewayCallContext takes the config-bypass path and context.config is {}, so authMode resolves to the default token. This check then treats the supplied password as inactive shared auth, keeps the durable device identity attached, and password-protected local CLI/backend calls can still create or hit a stale paired-device scope baseline—the token path is fixed, but the documented password path is not.
Useful? React with 👍 / 👎.
ef1a26e to
69bbf94
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69bbf942ec
ℹ️ 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".
| const isLocal = params.locality === "direct_local" || params.locality === "cli_container_local"; | ||
| const usesSharedSecretAuth = params.authMethod === "token" || params.authMethod === "password"; | ||
| return isLocal && !params.hasBrowserOriginHeader && params.sharedAuthOk && usesSharedSecretAuth; |
There was a problem hiding this comment.
Restrict CLI scope preservation to local Host requests
When a gateway is reached through a reverse proxy that connects to it over loopback but does not set forwarded headers, isLocalDirectRequest classifies the socket as direct_local even if the Host header is public/non-local (the handler only logs that case). This new direct_local allowance then lets any client claiming cli with a valid shared token/password keep self-declared operator.admin scopes without a device, so remote proxied CLI-shaped requests can bypass the stale-device scope baseline that non-local shared-auth clients were previously denied.
Useful? React with 👍 / 👎.
69bbf94 to
b1c03dd
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1c03dd8a7
ℹ️ 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".
| (params.authMode === "token" && Boolean(params.token)) || | ||
| (params.authMode === "password" && Boolean(params.password)); |
There was a problem hiding this comment.
Include trusted-proxy fallback passwords in CLI identity omission
In a local Gateway configured with gateway.auth.mode: "trusted-proxy" plus the supported local password fallback, callGateway resolves and sends the password, and the server authorizes the loopback request as authMethod: "password". Because this predicate only treats authMode === "password" as active shared-secret auth, those local CLI password calls still attach the durable device identity and can hit the stale read-scoped pairing baseline that this change is meant to bypass for password-auth CLI calls. This is separate from the explicit URL-override password case; the trusted-proxy local password fallback is covered by existing credential/auth tests and needs to be included here or derived from the resolved credential path.
Useful? React with 👍 / 👎.
b1c03dd to
9c31064
Compare
9c31064 to
55503a7
Compare
8a208b5 to
531725e
Compare
531725e to
e9509ee
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9509eec96
ℹ️ 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".
| const hasSharedSecretAuth = | ||
| (params.authMode === "token" && Boolean(params.token)) || | ||
| (params.authMode === "password" && Boolean(params.password)); |
There was a problem hiding this comment.
Accept trusted-proxy password fallback for backend auth
When gateway.auth.mode is trusted-proxy with the supported local password fallback, resolveGatewayCredentials still supplies password and the server authorizes clean loopback fallback as authMethod: "password", but this predicate leaves hasSharedSecretAuth false because authMode is not "password". Fresh evidence beyond the earlier CLI fallback issue is that backend callers with requireLocalBackendSharedAuth then hit the guard below and throw before opening the websocket, breaking node pairing/list paths that intentionally use local backend shared auth under trusted-proxy deployments.
Useful? React with 👍 / 👎.
|
Merged via squash.
|
…96002) * fix(gateway): keep local CLI shared auth off device scopes * fix(gateway): preserve auth-none CLI device identity --------- Co-authored-by: Peter Steinberger <[email protected]>
Fixes #95997
What Problem This Solves
Local CLI Gateway calls could poison the durable CLI device scope baseline: a read-scoped
gateway healthcall could leave the CLI paired atoperator.read, then a later write-scopedagentcall hitscope upgrade pending approvalon the primary Gateway path.Why This Change Was Made
The fix keeps the existing paired-device scope-upgrade protection intact and moves the local CLI shared-token/password path out of durable device identity. That matches the backend local shared-auth carve-out without allowing generic device-less shared-token clients to self-declare scopes.
Concretely:
User Impact
Users and release operators can run read-scoped local CLI probes before agent commands without blocking the primary Gateway agent path behind a stale
operator.readpairing baseline. Remote/device-token scope upgrades remain explicit.Evidence
node scripts/run-vitest.mjs src/gateway/call.test.ts src/gateway/server/ws-connection/handshake-auth-helpers.test.ts src/gateway/server.auth.compat-baseline.test.ts src/gateway/server.silent-scope-upgrade-reconnect.poc.test.tsnode scripts/crabbox-wrapper.mjs run --provider blacksmith-testbox -- env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changedblacksmith-testbox, idtbx_01kvskg4kfk1p575p8shahtxmv, runhttps://github.com/openclaw/openclaw/actions/runs/28007575695, exit 0..agents/skills/autoreview/scripts/autoreview --mode local --base origin/main