Skip to content

Commit 0125ce1

Browse files
authored
Gateway: fail closed unresolved local auth SecretRefs (openclaw#42672)
* Gateway: fail closed unresolved local auth SecretRefs * Docs: align node-host gateway auth precedence * CI: resolve rebase breakages in checks lanes * Tests: isolate LOCAL_REMOTE_FALLBACK_TOKEN env state * Gateway: remove stale remote.enabled auth-surface semantics * Changelog: note gateway SecretRef fail-closed fix
1 parent a52104c commit 0125ce1

File tree

20 files changed

+197
-48
lines changed

20 files changed

+197
-48
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ Docs: https://docs.openclaw.ai
8484
- Browser/Browserbase 429 handling: surface stable no-retry rate-limit guidance without buffering discarded HTTP 429 response bodies from remote browser services. (#40491) thanks @mvanhorn.
8585
- Gateway/auth: allow one trusted device-token retry on shared-token mismatch with recovery hints to prevent reconnect churn during token drift. (#42507) Thanks @joshavant.
8686
- Channels/allowlists: remove stale matcher caching so same-array allowlist edits and wildcard replacements take effect immediately, with regression coverage for in-place mutation cases.
87-
- Gateway/auth: fail closed when local `gateway.auth.*` SecretRefs are configured but unavailable, instead of silently falling back to `gateway.remote.*` credentials in local mode. Thanks @tdjackey.
87+
- Gateway/auth: fail closed when local `gateway.auth.*` SecretRefs are configured but unavailable, instead of silently falling back to `gateway.remote.*` credentials in local mode. (#42672) Thanks @joshavant.
8888
- Sandbox/fs bridge: pin staged writes to verified parent directories so temporary write files cannot materialize outside the allowed mount before atomic replace. Thanks @tdjackey.
8989
- Commands/config writes: enforce `configWrites` against both the originating account and the targeted account scope for `/config` and config-backed `/allowlist` edits, blocking sibling-account mutations while preserving gateway `operator.admin` flows. Thanks @tdjackey for reporting.
9090

apps/macos/Sources/OpenClawProtocol/GatewayModels.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,8 @@ public struct SessionsPatchParams: Codable, Sendable {
13371337
public let model: AnyCodable?
13381338
public let spawnedby: AnyCodable?
13391339
public let spawndepth: AnyCodable?
1340+
public let subagentrole: AnyCodable?
1341+
public let subagentcontrolscope: AnyCodable?
13401342
public let sendpolicy: AnyCodable?
13411343
public let groupactivation: AnyCodable?
13421344

@@ -1355,6 +1357,8 @@ public struct SessionsPatchParams: Codable, Sendable {
13551357
model: AnyCodable?,
13561358
spawnedby: AnyCodable?,
13571359
spawndepth: AnyCodable?,
1360+
subagentrole: AnyCodable?,
1361+
subagentcontrolscope: AnyCodable?,
13581362
sendpolicy: AnyCodable?,
13591363
groupactivation: AnyCodable?)
13601364
{
@@ -1372,6 +1376,8 @@ public struct SessionsPatchParams: Codable, Sendable {
13721376
self.model = model
13731377
self.spawnedby = spawnedby
13741378
self.spawndepth = spawndepth
1379+
self.subagentrole = subagentrole
1380+
self.subagentcontrolscope = subagentcontrolscope
13751381
self.sendpolicy = sendpolicy
13761382
self.groupactivation = groupactivation
13771383
}
@@ -1391,6 +1397,8 @@ public struct SessionsPatchParams: Codable, Sendable {
13911397
case model
13921398
case spawnedby = "spawnedBy"
13931399
case spawndepth = "spawnDepth"
1400+
case subagentrole = "subagentRole"
1401+
case subagentcontrolscope = "subagentControlScope"
13941402
case sendpolicy = "sendPolicy"
13951403
case groupactivation = "groupActivation"
13961404
}
@@ -3046,7 +3054,7 @@ public struct ExecApprovalsSnapshot: Codable, Sendable {
30463054

30473055
public struct ExecApprovalRequestParams: Codable, Sendable {
30483056
public let id: String?
3049-
public let command: String
3057+
public let command: String?
30503058
public let commandargv: [String]?
30513059
public let systemrunplan: [String: AnyCodable]?
30523060
public let env: [String: AnyCodable]?
@@ -3067,7 +3075,7 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
30673075

30683076
public init(
30693077
id: String?,
3070-
command: String,
3078+
command: String?,
30713079
commandargv: [String]?,
30723080
systemrunplan: [String: AnyCodable]?,
30733081
env: [String: AnyCodable]?,

apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,8 @@ public struct SessionsPatchParams: Codable, Sendable {
13371337
public let model: AnyCodable?
13381338
public let spawnedby: AnyCodable?
13391339
public let spawndepth: AnyCodable?
1340+
public let subagentrole: AnyCodable?
1341+
public let subagentcontrolscope: AnyCodable?
13401342
public let sendpolicy: AnyCodable?
13411343
public let groupactivation: AnyCodable?
13421344

@@ -1355,6 +1357,8 @@ public struct SessionsPatchParams: Codable, Sendable {
13551357
model: AnyCodable?,
13561358
spawnedby: AnyCodable?,
13571359
spawndepth: AnyCodable?,
1360+
subagentrole: AnyCodable?,
1361+
subagentcontrolscope: AnyCodable?,
13581362
sendpolicy: AnyCodable?,
13591363
groupactivation: AnyCodable?)
13601364
{
@@ -1372,6 +1376,8 @@ public struct SessionsPatchParams: Codable, Sendable {
13721376
self.model = model
13731377
self.spawnedby = spawnedby
13741378
self.spawndepth = spawndepth
1379+
self.subagentrole = subagentrole
1380+
self.subagentcontrolscope = subagentcontrolscope
13751381
self.sendpolicy = sendpolicy
13761382
self.groupactivation = groupactivation
13771383
}
@@ -1391,6 +1397,8 @@ public struct SessionsPatchParams: Codable, Sendable {
13911397
case model
13921398
case spawnedby = "spawnedBy"
13931399
case spawndepth = "spawnDepth"
1400+
case subagentrole = "subagentRole"
1401+
case subagentcontrolscope = "subagentControlScope"
13941402
case sendpolicy = "sendPolicy"
13951403
case groupactivation = "groupActivation"
13961404
}
@@ -3046,7 +3054,7 @@ public struct ExecApprovalsSnapshot: Codable, Sendable {
30463054

30473055
public struct ExecApprovalRequestParams: Codable, Sendable {
30483056
public let id: String?
3049-
public let command: String
3057+
public let command: String?
30503058
public let commandargv: [String]?
30513059
public let systemrunplan: [String: AnyCodable]?
30523060
public let env: [String: AnyCodable]?
@@ -3067,7 +3075,7 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
30673075

30683076
public init(
30693077
id: String?,
3070-
command: String,
3078+
command: String?,
30713079
commandargv: [String]?,
30723080
systemrunplan: [String: AnyCodable]?,
30733081
env: [String: AnyCodable]?,

docs/channels/discord.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ Default slash command settings:
946946
Gateway auth for this handler uses the same shared credential resolution contract as other Gateway clients:
947947

948948
- env-first local auth (`OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD` then `gateway.auth.*`)
949-
- in local mode, `gateway.remote.*` can be used as fallback when `gateway.auth.*` is unset
949+
- in local mode, `gateway.remote.*` can be used as fallback only when `gateway.auth.*` is unset; configured-but-unresolved local SecretRefs fail closed
950950
- remote-mode support via `gateway.remote.*` when applicable
951951
- URL overrides are override-safe: CLI overrides do not reuse implicit credentials, and env overrides use env credentials only
952952

docs/cli/acp.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ Security note:
273273
- `--token` and `--password` can be visible in local process listings on some systems.
274274
- Prefer `--token-file`/`--password-file` or environment variables (`OPENCLAW_GATEWAY_TOKEN`, `OPENCLAW_GATEWAY_PASSWORD`).
275275
- Gateway auth resolution follows the shared contract used by other Gateway clients:
276-
- local mode: env (`OPENCLAW_GATEWAY_*`) -> `gateway.auth.*` -> `gateway.remote.*` fallback when `gateway.auth.*` is unset
276+
- local mode: env (`OPENCLAW_GATEWAY_*`) -> `gateway.auth.*` -> `gateway.remote.*` fallback only when `gateway.auth.*` is unset (configured-but-unresolved local SecretRefs fail closed)
277277
- remote mode: `gateway.remote.*` with env/config fallback per remote precedence rules
278278
- `--url` is override-safe and does not reuse implicit config/env credentials; pass explicit `--token`/`--password` (or file variants)
279279
- ACP runtime backend child processes receive `OPENCLAW_SHELL=acp`, which can be used for context-specific shell/profile rules.

docs/cli/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,7 @@ Subcommands:
10181018

10191019
Auth notes:
10201020

1021-
- `node` resolves gateway auth from env/config (no `--token`/`--password` flags): `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD`, then `gateway.auth.*`, with remote-mode support via `gateway.remote.*`.
1021+
- `node` resolves gateway auth from env/config (no `--token`/`--password` flags): `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD`, then `gateway.auth.*`. In local mode, node host intentionally ignores `gateway.remote.*`; in `gateway.mode=remote`, `gateway.remote.*` participates per remote precedence rules.
10221022
- Legacy `CLAWDBOT_GATEWAY_*` env vars are intentionally ignored for node-host auth resolution.
10231023

10241024
## Nodes

docs/cli/node.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ Options:
6464

6565
- `OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD` are checked first.
6666
- Then local config fallback: `gateway.auth.token` / `gateway.auth.password`.
67-
- In local mode, `gateway.remote.token` / `gateway.remote.password` are also eligible as fallback when `gateway.auth.*` is unset.
67+
- In local mode, node host intentionally does not inherit `gateway.remote.token` / `gateway.remote.password`.
68+
- If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, node auth resolution fails closed (no remote fallback masking).
6869
- In `gateway.mode=remote`, remote client fields (`gateway.remote.token` / `gateway.remote.password`) are also eligible per remote precedence rules.
6970
- Legacy `CLAWDBOT_GATEWAY_*` env vars are ignored for node host auth resolution.
7071

docs/gateway/configuration-reference.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2470,7 +2470,8 @@ See [Plugins](/tools/plugin).
24702470
- `remote.transport`: `ssh` (default) or `direct` (ws/wss). For `direct`, `remote.url` must be `ws://` or `wss://`.
24712471
- `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`: client-side break-glass override that allows plaintext `ws://` to trusted private-network IPs; default remains loopback-only for plaintext.
24722472
- `gateway.remote.token` / `.password` are remote-client credential fields. They do not configure gateway auth by themselves.
2473-
- Local gateway call paths can use `gateway.remote.*` as fallback when `gateway.auth.*` is unset.
2473+
- Local gateway call paths can use `gateway.remote.*` as fallback only when `gateway.auth.*` is unset.
2474+
- If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking).
24742475
- `trustedProxies`: reverse proxy IPs that terminate TLS. Only list proxies you control.
24752476
- `allowRealIpFallback`: when `true`, the gateway accepts `X-Real-IP` if `X-Forwarded-For` is missing. Default `false` for fail-closed behavior.
24762477
- `gateway.tools.deny`: extra tool names blocked for HTTP `POST /tools/invoke` (extends default deny list).

docs/gateway/remote.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,19 @@ When the gateway is loopback-only, keep the URL at `ws://127.0.0.1:18789` and op
103103

104104
## Credential precedence
105105

106-
Gateway credential resolution follows one shared contract across call/probe/status paths, Discord exec-approval monitoring, and node-host connections:
106+
Gateway credential resolution follows one shared contract across call/probe/status paths and Discord exec-approval monitoring. Node-host uses the same base contract with one local-mode exception (it intentionally ignores `gateway.remote.*`):
107107

108108
- Explicit credentials (`--token`, `--password`, or tool `gatewayToken`) always win on call paths that accept explicit auth.
109109
- URL override safety:
110110
- CLI URL overrides (`--url`) never reuse implicit config/env credentials.
111111
- Env URL overrides (`OPENCLAW_GATEWAY_URL`) may use env credentials only (`OPENCLAW_GATEWAY_TOKEN` / `OPENCLAW_GATEWAY_PASSWORD`).
112112
- Local mode defaults:
113-
- token: `OPENCLAW_GATEWAY_TOKEN` -> `gateway.auth.token` -> `gateway.remote.token`
114-
- password: `OPENCLAW_GATEWAY_PASSWORD` -> `gateway.auth.password` -> `gateway.remote.password`
113+
- token: `OPENCLAW_GATEWAY_TOKEN` -> `gateway.auth.token` -> `gateway.remote.token` (remote fallback applies only when local auth token input is unset)
114+
- password: `OPENCLAW_GATEWAY_PASSWORD` -> `gateway.auth.password` -> `gateway.remote.password` (remote fallback applies only when local auth password input is unset)
115115
- Remote mode defaults:
116116
- token: `gateway.remote.token` -> `OPENCLAW_GATEWAY_TOKEN` -> `gateway.auth.token`
117117
- password: `OPENCLAW_GATEWAY_PASSWORD` -> `gateway.remote.password` -> `gateway.auth.password`
118+
- Node-host local-mode exception: `gateway.remote.token` / `gateway.remote.password` are ignored.
118119
- Remote probe/status token checks are strict by default: they use `gateway.remote.token` only (no local token fallback) when targeting remote mode.
119120
- Legacy `CLAWDBOT_GATEWAY_*` env vars are only used by compatibility call paths; probe/status/auth resolution uses `OPENCLAW_GATEWAY_*` only.
120121

@@ -140,7 +141,8 @@ Short version: **keep the Gateway loopback-only** unless you’re sure you need
140141
set `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` on the client process as break-glass.
141142
- **Non-loopback binds** (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) must use auth tokens/passwords.
142143
- `gateway.remote.token` / `.password` are client credential sources. They do **not** configure server auth by themselves.
143-
- Local call paths can use `gateway.remote.*` as fallback when `gateway.auth.*` is unset.
144+
- Local call paths can use `gateway.remote.*` as fallback only when `gateway.auth.*` is unset.
145+
- If `gateway.auth.token` / `gateway.auth.password` is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking).
144146
- `gateway.remote.tlsFingerprint` pins the remote TLS cert when using `wss://`.
145147
- **Tailscale Serve** can authenticate Control UI/WebSocket traffic via identity
146148
headers when `gateway.auth.allowTailscale: true`; HTTP API endpoints still

docs/gateway/secrets.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ Examples of inactive surfaces:
4141
- Web search provider-specific keys that are not selected by `tools.web.search.provider`.
4242
In auto mode (provider unset), keys are consulted by precedence for provider auto-detection until one resolves.
4343
After selection, non-selected provider keys are treated as inactive until selected.
44-
- `gateway.remote.token` / `gateway.remote.password` SecretRefs are active (when `gateway.remote.enabled` is not `false`) if one of these is true:
44+
- `gateway.remote.token` / `gateway.remote.password` SecretRefs are active if one of these is true:
4545
- `gateway.mode=remote`
4646
- `gateway.remote.url` is configured
4747
- `gateway.tailscale.mode` is `serve` or `funnel`
48-
In local mode without those remote surfaces:
49-
- `gateway.remote.token` is active when token auth can win and no env/auth token is configured.
50-
- `gateway.remote.password` is active only when password auth can win and no env/auth password is configured.
48+
- In local mode without those remote surfaces:
49+
- `gateway.remote.token` is active when token auth can win and no env/auth token is configured.
50+
- `gateway.remote.password` is active only when password auth can win and no env/auth password is configured.
5151
- `gateway.auth.token` SecretRef is inactive for startup auth resolution when `OPENCLAW_GATEWAY_TOKEN` (or `CLAWDBOT_GATEWAY_TOKEN`) is set, because env token input wins for that runtime.
5252

5353
## Gateway auth surface diagnostics

0 commit comments

Comments
 (0)