Skip to content

fix(gateway): skip device pairing for local backend self-connections#30801

Merged
vincentkoc merged 4 commits intoopenclaw:mainfrom
Sid-Qin:fix/30740-subagent-tls-pairing
Mar 2, 2026
Merged

fix(gateway): skip device pairing for local backend self-connections#30801
vincentkoc merged 4 commits intoopenclaw:mainfrom
Sid-Qin:fix/30740-subagent-tls-pairing

Conversation

@Sid-Qin
Copy link
Contributor

@Sid-Qin Sid-Qin commented Mar 1, 2026

Summary

  • Problem: When gateway.tls.enabled=true (with autoGenerate: true), sessions_spawn and other internal callGateway operations fail with "pairing required" (WebSocket close code 1008). The gateway treats its own internal self-connection like an external client and enforces device pairing.
  • Why it matters: Sub-agent spawning is completely broken when TLS is enabled in Docker with bind: "lan". Without TLS, spawn fails with SECURITY ERROR (ws:// to non-loopback). With TLS, spawn fails with pairing required. Users have no workaround.
  • What changed: src/gateway/server/ws-connection/message-handler.ts — detect backend self-connections (gateway-client from localhost with valid shared auth) and skip pairing for them.
  • What did NOT change: Pairing behavior for external clients (CLI, Control UI, mobile apps); TLS security checks; auth validation logic.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • 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

User-visible / Behavior Changes

  • sessions_spawn now works when gateway.tls.enabled=true with autoGenerate: true
  • Sub-agent spawning works in Docker with bind: "lan" + TLS

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No — only skips pairing for connections that already passed shared auth
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

The pairing bypass is gated on three conditions: (1) client ID is gateway-client, (2) shared auth (token/password) succeeded, and (3) connection is from localhost. External clients still require pairing.

Repro + Verification

Environment

  • OS: Linux (Docker container)
  • Runtime: Node.js
  • Integration/channel: Any (sub-agent spawning)

Steps

  1. Configure gateway with tls: { enabled: true, autoGenerate: true } and bind: "lan"
  2. Call sessions_spawn from within a session
  3. Observe: sub-agent spawns successfully instead of failing with "pairing required"

Expected

  • Sub-agent connects to wss://127.0.0.1:port and completes handshake

Actual

  • Before fix: Gateway closes connection with 1008: pairing required
  • After fix: Gateway skips pairing for authenticated local backend self-connections

Evidence

// The fix adds a backend self-connection detection before the pairing block:
const isBackendSelfConnection =
  connectParams.client.id === GATEWAY_CLIENT_IDS.GATEWAY_CLIENT &&
  sharedAuthOk &&
  isLocalClient;
const skipPairing =
  isBackendSelfConnection ||
  shouldSkipControlUiPairing(controlUiAuthPolicy, sharedAuthOk, trustedProxyAuthOk);

Human Verification (required)

  • Verified scenarios: Backend self-connection with valid token from localhost skips pairing
  • Edge cases checked: External gateway-client connections still require pairing; non-gateway-client IDs unaffected; failed auth does not skip pairing
  • What I did not verify: Live Docker TLS setup with sub-agent spawn (tested logic path only)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert: Remove the isBackendSelfConnection check and revert skipPairing logic
  • Files/config to restore: src/gateway/server/ws-connection/message-handler.ts
  • Known bad symptoms: Sub-agent spawning would fail again with TLS enabled

Risks and Mitigations

Risk: A malicious process on the same host could impersonate gateway-client and skip pairing.
Mitigation: The bypass requires valid shared auth (token/password) AND localhost origin. An attacker with the token from the same host already has full gateway access.

@aisle-research-bot
Copy link

aisle-research-bot bot commented Mar 1, 2026

🔒 Aisle Security Analysis

✅ We scanned this PR and did not find any security vulnerabilities.

Aisle supplements but does not replace security review.


Analyzed PR: #30801 at commit 7575cfa

Copy link

@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: 7575cfa66a

ℹ️ 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".

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 1, 2026

Greptile Summary

This PR includes two separate fixes that should ideally be in separate PRs:

Gateway Fix (matches PR title): Adds logic to skip device pairing for backend self-connections when the gateway connects to itself. The bypass is gated on three conditions: client ID is gateway-client, valid shared auth, and connection from localhost. This is secure because an attacker on localhost with the shared token already has full gateway access.

Cron Fix (not mentioned in PR description): Addresses a croner library bug where nextRun returns past-year timestamps for certain timezone/date combinations (e.g., Asia/Shanghai). Adds multi-stage retry logic when the returned timestamp is not in the future.

Both fixes are functionally correct, but mixing unrelated changes in a single PR makes review and git history harder to follow.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk. The code changes are correct and well-tested.
  • Score reflects solid implementation of both fixes with proper security gating and test coverage. Reduced by one point for mixing unrelated changes in a single PR, which is a process concern rather than a code quality issue.
  • No files require special attention - both the gateway and cron changes are straightforward and well-tested.

Last reviewed commit: 7575cfa

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

SidQin-cyber and others added 3 commits March 1, 2026 21:12
When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740
@vincentkoc vincentkoc force-pushed the fix/30740-subagent-tls-pairing branch from 7575cfa to b069d0f Compare March 2, 2026 05:18
@vincentkoc
Copy link
Member

@aisle-research-bot review

@aisle-research-bot
Copy link

aisle-research-bot bot commented Mar 2, 2026

🔒 Aisle Security Analysis

✅ We scanned this PR and did not find any security vulnerabilities.

Aisle supplements but does not replace security review.


Analyzed PR: #30801 at commit b069d0f

Last updated on: 2026-03-02T05:42:50Z

@vincentkoc vincentkoc merged commit e1e715c into openclaw:main Mar 2, 2026
21 checks passed
safzanpirani pushed a commit to safzanpirani/clawdbot that referenced this pull request Mar 2, 2026
…penclaw#30801)

* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
steipete pushed a commit to Sid-Qin/openclaw that referenced this pull request Mar 2, 2026
…penclaw#30801)

* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
hanqizheng pushed a commit to hanqizheng/openclaw that referenced this pull request Mar 2, 2026
…penclaw#30801)

* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
execute008 pushed a commit to execute008/openclaw that referenced this pull request Mar 2, 2026
…penclaw#30801)

* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
…penclaw#30801)

* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
sachinkundu pushed a commit to sachinkundu/openclaw that referenced this pull request Mar 6, 2026
…penclaw#30801)

* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
…penclaw#30801)

* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
atlastacticalbot pushed a commit to tensakulabs/atlasbot that referenced this pull request Mar 6, 2026
…penclaw#30801)

* fix(gateway): skip device pairing for local backend self-connections

When gateway.tls is enabled, sessions_spawn (and other internal
callGateway operations) creates a new WebSocket to the gateway.
The gateway treated this self-connection like any external client
and enforced device pairing, rejecting it with "pairing required"
(close code 1008). This made sub-agent spawning impossible when
TLS was enabled in Docker with bind: "lan".

Skip pairing for connections that are gateway-client self-connections
from localhost with valid shared auth (token/password). These are
internal backend calls (e.g. sessions_spawn, subagent-announce) that
already have valid credentials and connect from the same host.

Closes openclaw#30740

* gateway: tighten backend self-pair bypass guard

* tests: cover backend self-pairing local-vs-remote auth path

* changelog: add gateway tls pairing fix credit

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
(cherry picked from commit e1e715c)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Subagent spawn fails with pairing required when gateway TLS is enabled (Docker)

2 participants