Skip to content

fix(gateway): skip device pairing when auth.mode=none#43478

Merged
ademczuk merged 1 commit intoopenclaw:mainfrom
ademczuk:fix/skip-pairing-when-auth-none
Mar 14, 2026
Merged

fix(gateway): skip device pairing when auth.mode=none#43478
ademczuk merged 1 commit intoopenclaw:mainfrom
ademczuk:fix/skip-pairing-when-auth-none

Conversation

@ademczuk
Copy link
Copy Markdown
Contributor

Summary

  • Skip device-pairing enforcement when gateway.auth.mode=none so Control UI connections behind reverse proxies (Tailscale Serve, Cloudflare Tunnel, etc.) no longer get stuck on pairing required (code 1008) despite auth being explicitly disabled.

Root Cause

sharedAuthOk in auth-context.ts only recognises password and token methods. When auth.mode=none, authMethod is "none" and sharedAuthOk stays false, so shouldSkipBackendSelfPairing never fires. The skipPairing computation then falls through to requiring device pairing - which contradicts the operator's explicit intent to disable all auth.

Fix

Add resolvedAuth.mode === "none" as the first condition in the skipPairing computation in message-handler.ts. When auth is explicitly disabled, pairing (which is itself an auth mechanism) must also be skipped.

Test plan

  • pnpm check passes locally (types + lint + format)
  • Gateway ws-connection tests pass (13/13)
  • Manual: set gateway.auth.mode=none + connect remote Control UI - should connect without pairing prompt

Fixes #42931

CHANGELOG

- Gateway/websocket pairing bypass for disabled auth: skip device-pairing enforcement when `gateway.auth.mode=none` so Control UI connections behind reverse proxies no longer get stuck on `pairing required` (code 1008) despite auth being explicitly disabled. (#42931)

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: XS labels Mar 11, 2026
@ademczuk ademczuk force-pushed the fix/skip-pairing-when-auth-none branch from 797e90b to 124acdb Compare March 11, 2026 21:24
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 11, 2026

Greptile Summary

This PR fixes a bug where gateway WebSocket connections using device identity were incorrectly stuck on pairing required (close code 1008) even when the operator had explicitly disabled all authentication via gateway.auth.mode=none. The root cause was that the skipPairing computation relied entirely on sharedAuthOk, which is always false when authMethod is "none" (neither "token" nor "password"), so neither shouldSkipBackendSelfPairing nor shouldSkipControlUiPairing ever fired.

Changes:

  • message-handler.ts: Adds resolvedAuth.mode === "none" as the first, short-circuit condition in the skipPairing boolean. Since resolvedAuth is server-side configuration the client cannot influence, this is safe — and logically necessary, as device pairing is itself an authentication mechanism that must be bypassed when the operator has opted out of all auth.
  • CHANGELOG.md: Entry added to the unreleased section describing the fix.

Notes:

  • No new automated regression test was added for this scenario (only a manual verification step is listed in the test plan). A test covering auth.mode=none + unpaired device identity would guard against future regressions.
  • A related (but out-of-scope) edge case exists in evaluateMissingDeviceIdentity: a remote Control UI connection with auth.mode=none and no device identity would still be rejected with "control ui requires device identity" because sharedAuthOk is false and controlUiAuthPolicy.allowBypass is false. The PR's stated scope (reverse-proxy Control UI that already supplies device identity) is not affected by this, but operators who also cannot rely on SubtleCrypto may encounter a separate blocker.

Confidence Score: 4/5

  • The fix is correct and minimal — safe to merge with the suggestion of adding a regression test.
  • The one-line logic change is type-safe (using the existing ResolvedGatewayAuthMode union), consistent with how resolvedAuth.mode is used elsewhere in the file, and only fires for a server-side configuration value that clients cannot spoof. The existing test suite still passes. Score reduced by one point solely because there is no new automated test covering the repaired scenario.
  • No files require special attention beyond the missing regression test noted in src/gateway/server/ws-connection/message-handler.ts.

Last reviewed commit: 124acdb

Comment thread src/gateway/server/ws-connection/message-handler.ts Outdated
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: 124acdb742

ℹ️ 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 thread src/gateway/server/ws-connection/message-handler.ts
@ademczuk
Copy link
Copy Markdown
Contributor Author

ademczuk commented Mar 12, 2026

Security context: pairing bypass when auth.mode=none

auth.mode=none means the operator explicitly opted out of all authentication. Pairing is itself an auth mechanism - enforcing it when auth is disabled contradicts the operator's intent and blocks the documented use case (reverse proxy connections via Tailscale Serve, Cloudflare Tunnel, etc.).

The skipPairing addition at line 680 of message-handler.ts only fires when resolvedAuth.mode === "none". When the operator later re-enables auth (token/password), the condition no longer matches and pairing enforcement resumes normally.

@ademczuk ademczuk requested a review from altaywtf March 12, 2026 00:55
@ademczuk ademczuk force-pushed the fix/skip-pairing-when-auth-none branch from 24e0fe2 to bc0ed03 Compare March 12, 2026 06:03
@openclaw-barnacle openclaw-barnacle Bot added the maintainer Maintainer-authored PR label Mar 12, 2026
@ademczuk ademczuk force-pushed the fix/skip-pairing-when-auth-none branch from bc0ed03 to 0b185a2 Compare March 13, 2026 17:34
@ademczuk ademczuk requested a review from joshavant March 13, 2026 17:34
altaywtf
altaywtf previously approved these changes Mar 13, 2026
Copy link
Copy Markdown
Member

@altaywtf altaywtf left a comment

Choose a reason for hiding this comment

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

lgtm

@ademczuk ademczuk force-pushed the fix/skip-pairing-when-auth-none branch from 0b185a2 to 5c82731 Compare March 13, 2026 21:07
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Mar 13, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Unauthenticated access when gateway.auth.mode=none now bypasses device pairing, enabling self-asserted operator scopes

1. 🟠 Unauthenticated access when gateway.auth.mode=none now bypasses device pairing, enabling self-asserted operator scopes

Property Value
Severity High
CWE CWE-284
Location src/gateway/server/ws-connection/message-handler.ts:677-688

Description

In the WebSocket handshake, device pairing is a key authorization gate that prevents an arbitrary device identity from gaining operator privileges (role/scope upgrades require an approved pairing record). The change makes gateway.auth.mode=none unconditionally skip this pairing enforcement.

As a result, when gateway.auth.mode=none is configured:

  • Any network client can generate its own device keypair and sign a valid connectParams.device payload (device identity is self-asserted without pairing)
  • The client can self-declare connectParams.scopes (e.g. operator.admin) and keep them because a device identity is present
  • Pairing checks that would normally reject with WS close code 1008 "pairing required" are skipped, so the client can immediately call privileged gateway methods

This is a meaningful weakening of access controls even when “auth is disabled”, because pairing previously acted as a defense-in-depth approval gate for new devices and for role/scope upgrades.

Vulnerable change:

// auth.mode=none disables all authentication — device pairing is an// auth mechanism and must also be skipped when the operator opted out.
const skipPairing =
  resolvedAuth.mode === "none" ||
  shouldSkipBackendSelfPairing(...) ||
  shouldSkipControlUiPairing(...);

if (device && devicePublicKey && !skipPairing) {// ... requirePairing(...); close(1008, "pairing required")
}

Security context that still exists but is not sufficient as a replacement:

  • Origin checks are enforced only for browser-originated connections / Control UI / WebChat; non-browser clients without an Origin header are not origin-restricted.
  • Method authorization is scope-based (handleGatewayRequest), but scopes come from the handshake (connectParams.scopes) and are effectively user-controlled if pairing is skipped.

If an operator sets gateway.auth.mode=none behind a reverse proxy (common for “proxy does auth”), any proxy bypass (direct-to-backend access, misrouting, SSRF to localhost, firewall hole) becomes a full control-plane compromise.

Recommendation

Treat auth.mode=none as dangerous and keep a hard, explicit barrier before allowing privileged scopes.

Options (pick at least one):

  1. Do not skip pairing by default in none mode. Introduce a separate explicit flag (e.g. gateway.controlUi.dangerouslyDisablePairing or gateway.auth.dangerouslyAllowUnpairedDevices) that must be set to bypass pairing.

  2. If auth.mode=none, force least privilege by clearing/ignoring requested scopes unless the device is already paired:

const isOpenAuth = resolvedAuth.mode === "none";

if (isOpenAuth && device && devicePublicKey) {
  const paired = await getPairedDevice(device.id);
  const isPaired = paired?.publicKey === devicePublicKey;
  if (!isPaired) {// prevent self-asserted privileges in open-auth mode
    connectParams.scopes = [];
    scopes = [];
  }
}
  1. Add startup/runtime warnings and/or refuse to bind beyond loopback when auth.mode=none unless an explicit acknowledgement flag is set (similar to other dangerously* config flags).

Additionally, strongly recommend using gateway.auth.mode=trusted-proxy (with gateway.trustedProxies configured) rather than none for reverse-proxy deployments, so the gateway enforces an identity boundary itself.


Analyzed PR: #43478 at commit cfd8561

Last updated on: 2026-03-14T07:17:51Z

@ademczuk ademczuk force-pushed the fix/skip-pairing-when-auth-none branch 2 times, most recently from e688137 to 6c16cbc Compare March 13, 2026 21:13
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: e688137174

ℹ️ 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 thread src/gateway/server/ws-connection/message-handler.ts
@ademczuk
Copy link
Copy Markdown
Contributor Author

ademczuk commented Mar 13, 2026

Rebased onto latest main. CHANGELOG entry verified. All code checks, extensions, and protocol pass. The node test shard and Windows shard failures are from the temp-path-guard.test.ts regression on main (da1ec45505) - same failures on every open PR in the repo, including PRs that maintainers are actively merging.

@ademczuk ademczuk force-pushed the fix/skip-pairing-when-auth-none branch from 6c16cbc to 2efd396 Compare March 13, 2026 22:39
@ademczuk
Copy link
Copy Markdown
Contributor Author

ademczuk commented Mar 14, 2026

Re: Aisle (CWE-284 - unauthenticated access bypasses device pairing)

When auth.mode=none, the operator has explicitly disabled all authentication. Device pairing is an auth mechanism - enforcing it when auth is disabled is contradictory. The skipPairing flag only applies while auth.mode is actively "none".

On the self-asserted scopes concern: connectParams.role and connectParams.scopes are already accepted without pairing verification on loopback connections (shouldSkipBackendSelfPairing) and on Control UI connections with valid shared auth (shouldSkipControlUiPairing). This PR adds a third skip condition consistent with the existing trust model.

The reverse proxy scenario (Tailscale Serve, Cloudflare Tunnel) is specifically called out in #42931. These operators handle auth at the proxy layer and expect the gateway to respect auth.mode=none.

When gateway.auth.mode is set to "none", authentication succeeds with
method "none" but sharedAuthOk remains false because the auth-context
only recognises token/password/trusted-proxy methods. This causes all
pairing-skip conditions to fail, so Control UI browser connections get
closed with code 1008 "pairing required" despite auth being disabled.

Short-circuit the skipPairing check: if the operator explicitly
disabled authentication, device pairing (which is itself an auth
mechanism) must also be bypassed.

Fixes openclaw#42931
@ademczuk ademczuk force-pushed the fix/skip-pairing-when-auth-none branch from 2efd396 to cfd8561 Compare March 14, 2026 06:56
@ademczuk
Copy link
Copy Markdown
Contributor Author

Heads up - altaywtf's approval was dismissed by the rebase force-push. The diff is unchanged from what was reviewed (same 1 commit, same 2 files). Just needs a re-approval when convenient.

@ademczuk ademczuk merged commit 9bffa34 into openclaw:main Mar 14, 2026
29 of 30 checks passed
ademczuk added a commit to ademczuk/openclaw that referenced this pull request Mar 14, 2026
The revert of openclaw#43478 (commit 39b4185) was silently undone by
3704293 which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
ademczuk added a commit that referenced this pull request Mar 14, 2026
The revert of #43478 (commit 39b4185) was silently undone by
3704293 which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
sibbl pushed a commit to sibbl/clawdbot that referenced this pull request Mar 15, 2026
The revert of openclaw#43478 (commit 39b4185) was silently undone by
3704293 which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
sibbl pushed a commit to sibbl/clawdbot that referenced this pull request Mar 15, 2026
…nclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.
romeroej2 pushed a commit to romeroej2/openclaw that referenced this pull request Mar 16, 2026
The revert of openclaw#43478 (commit 39b4185) was silently undone by
3704293 which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
romeroej2 pushed a commit to romeroej2/openclaw that referenced this pull request Mar 16, 2026
…nclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.
guiramos added a commit to butley/openclaw that referenced this pull request Mar 22, 2026
* feat: make compaction timeout configurable via agents.defaults.compaction.timeoutSeconds (openclaw#46889)

* feat: make compaction timeout configurable via agents.defaults.compaction.timeoutSeconds

The hardcoded 5-minute (300s) compaction timeout causes large sessions
to enter a death spiral where compaction repeatedly fails and the
session grows indefinitely. This adds agents.defaults.compaction.timeoutSeconds
to allow operators to override the compaction safety timeout.

Default raised to 900s (15min) which is sufficient for sessions up to
~400k tokens. The resolved timeout is also used for the session write
lock duration so locks don't expire before compaction completes.

Fixes openclaw#38233

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add resolveCompactionTimeoutMs tests

Cover config resolution edge cases: undefined config, missing
compaction section, valid seconds, fractional values, zero,
negative, NaN, and Infinity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add timeoutSeconds to compaction Zod schema

The compaction object schema uses .strict(), so setting the new
timeoutSeconds config option would fail validation at startup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: enforce integer constraint on compaction timeoutSeconds schema

Prevents sub-second values like 0.5 which would floor to 0ms and
cause immediate compaction timeout. Matches pattern of other
integer timeout fields in the schema.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: clamp compaction timeout to Node timer-safe maximum

Values above ~2.1B ms overflow Node's setTimeout to 1ms, causing
immediate timeout. Clamp to MAX_SAFE_TIMEOUT_MS matching the
pattern in agents/timeout.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add FIELD_LABELS entry for compaction timeoutSeconds

Maintains label/help parity invariant enforced by
schema.help.quality.test.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align compaction timeouts with abort handling

* fix: land compaction timeout handling (openclaw#46889) (thanks @asyncjason)

---------

Co-authored-by: Jason Separovic <jason@wilma.dog>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix: harden compaction timeout follow-ups

* Docs: fix stale Clawdbot branding in agent workflow file (openclaw#46963)

Co-authored-by: webdevpraveen <webdevpraveen@users.noreply.github.com>

* docs: replace outdated Clawdbot references with OpenClaw in skill docs (openclaw#41563)

Update 5 references to the old "Clawdbot" name in
skills/apple-reminders/SKILL.md and skills/imsg/SKILL.md.

Co-authored-by: imanisynapse <imanisynapse@gmail.com>

* Docs: switch README logo to SVG assets (openclaw#47049)

* fix: Disable strict mode tools for non-native openai-completions compatible APIs (openclaw#45497)

Merged via squash.

Prepared head SHA: 20fe05f
Co-authored-by: sahancava <57447079+sahancava@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn

* fix: forward forceDocument through sendPayload path (follow-up to openclaw#45111) (openclaw#47119)

Merged via squash.

Prepared head SHA: d791190
Co-authored-by: thepagent <262003297+thepagent@users.noreply.github.com>
Reviewed-by: @frankekn

* fix(android): support android node  `calllog.search` (openclaw#44073)

* fix(android): support android node  `calllog.search`

* fix(android): support android node calllog.search

* fix(android): wire callLog through shared surfaces

* fix: land Android callLog support (openclaw#44073) (thanks @lxk7280)

---------

Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix(whatsapp): restore append recency filter lost in extensions refactor, handle Long timestamps (openclaw#42588)

Merged via squash.

Prepared head SHA: 8ce59bb
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob

* fix(web): handle 515 Stream Error during WhatsApp QR pairing (openclaw#27910)

* fix(web): handle 515 Stream Error during WhatsApp QR pairing

getStatusCode() never unwrapped the lastDisconnect wrapper object,
so login.errorStatus was always undefined and the 515 restart path
in restartLoginSocket was dead code.

- Add err.error?.output?.statusCode fallback to getStatusCode()
- Export waitForCredsSaveQueue() so callers can await pending creds
- Await creds flush in restartLoginSocket before creating new socket

Fixes openclaw#3942

* test: update session mock for getStatusCode unwrap + waitForCredsSaveQueue

Mirror the getStatusCode fix (err.error?.output?.statusCode fallback)
in the test mock and export waitForCredsSaveQueue so restartLoginSocket
tests work correctly.

* fix(web): scope creds save queue per-authDir to avoid cross-account blocking

The credential save queue was a single global promise chain shared by all
WhatsApp accounts. In multi-account setups, a slow save on one account
blocked credential writes and 515 restart recovery for unrelated accounts.

Replace the global queue with a per-authDir Map so each account's creds
serialize independently. waitForCredsSaveQueue() now accepts an optional
authDir to wait on a single account's queue, or waits on all when omitted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: use real Baileys v7 error shape in 515 restart test

The test was using { output: { statusCode: 515 } } which was already
handled before the fix. Updated to use the actual Baileys v7 shape
{ error: { output: { statusCode: 515 } } } to cover the new fallback
path in getStatusCode.

Co-Authored-By: Claude Code (Opus 4.6) <noreply@anthropic.com>

* fix(web): bound credential-queue wait during 515 restart

Prevents restartLoginSocket from blocking indefinitely if a queued
saveCreds() promise stalls (e.g. hung filesystem write).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: clear flush timeout handle and assert creds queue in test

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: evict settled credsSaveQueues entries to prevent unbounded growth

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: share WhatsApp 515 creds flush handling (openclaw#27910) (thanks @asyncjason)

---------

Co-authored-by: Jason Separovic <jason@wilma.dog>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* Deduplicate repeated tool call IDs for OpenAI-compatible APIs (openclaw#40996)

Merged via squash.

Prepared head SHA: 38d8048
Co-authored-by: xaeon2026 <264572156+xaeon2026@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Reviewed-by: @frankekn

* fix(gateway): skip Control UI pairing when auth.mode=none (closes openclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.

* fix: preserve Telegram word boundaries when rechunking HTML (openclaw#47274)

* fix: preserve Telegram chunk word boundaries

* fix: address Telegram chunking review feedback

* fix: preserve Telegram retry separators

* fix: preserve Telegram chunking boundaries (openclaw#47274)

* test(whatsapp): fix stale append inbox expectation

* chore(gateway): ignore `.test.ts` changes in `gateway:watch` (openclaw#36211)

* fix: harden remote cdp probes

* feat(feishu): add ACP and subagent session binding (openclaw#46819)

* feat(feishu): add ACP session support

* fix(feishu): preserve sender-scoped ACP rebinding

* fix(feishu): recover sender scope from bound ACP sessions

* fix(feishu): support DM ACP binding placement

* feat(feishu): add current-conversation session binding

* fix(feishu): avoid DM parent binding fallback

* fix(feishu): require canonical topic sender ids

* fix(feishu): honor sender-scoped ACP bindings

* fix(feishu): allow user-id ACP DM bindings

* fix(feishu): recover user-id ACP DM bindings

* ACP: fail closed on conflicting tool identity hints (openclaw#46817)

* ACP: fail closed on conflicting tool identity hints

* ACP: restore rawInput fallback for safe tool resolution

* ACP tests: cover rawInput-only safe tool approval

* fix: harden mention pattern regex compilation

* Nodes: recheck queued actions before delivery (openclaw#46815)

* Nodes: recheck queued actions before delivery

* Nodes tests: cover pull-time policy recheck

* Nodes tests: type node policy mocks explicitly

* refactor: drop deprecated whatsapp mention pattern sdk helper

* added a fix for memory leak on 2gb ram (openclaw#46522)

* Nodes tests: prove pull-time policy revalidation

* fix: harden device token rotation denial paths

* style: format imported model helpers

* Plugins: preserve scoped ids and reserve bundled duplicates (openclaw#47413)

* Plugins: preserve scoped ids and reserve bundled duplicates

* Changelog: add plugin scoped id note

* Plugins: harden scoped install ids

* Plugins: reserve scoped install dirs

* Plugins: migrate legacy scoped update ids

* CLI: reduce channels add startup memory (openclaw#46784)

* CLI: lazy-load channel subcommand handlers

* Channels: defer add command dependencies

* CLI: skip status JSON plugin preload

* CLI: cover status JSON route preload

* Status: trim JSON security audit path

* Status: update JSON fast-path tests

* CLI: cover root help fast path

* CLI: fast-path root help

* Status: keep JSON security parity

* Status: restore JSON security tests

* CLI: document status plugin preload

* Channels: reuse Telegram account import

* Integrations: tighten inbound callback and allowlist checks (openclaw#46787)

* Integrations: harden inbound callback and allowlist handling

* Integrations: address review follow-ups

* Update CHANGELOG.md

* Mattermost: avoid command-gating open button callbacks

* ACP: require admin scope for mutating internal actions (openclaw#46789)

* ACP: require admin scope for mutating internal actions

* ACP: cover operator admin mutating actions

* ACP: gate internal status behind admin scope

* Changelog: add missing PR credits

* Changelog: add more unreleased PR numbers

* Subagents: restrict follow-up messaging scope (openclaw#46801)

* Subagents: restrict follow-up messaging scope

* Subagents: cover foreign-session follow-up sends

* Update CHANGELOG.md

* Webhooks: tighten pre-auth body handling (openclaw#46802)

* Webhooks: tighten pre-auth body handling

* Webhooks: clean up request body guards

* Tools: revalidate workspace-only patch targets (openclaw#46803)

* Tools: revalidate workspace-only patch targets

* Tests: narrow apply-patch delete-path assertion

* CLI: trim onboarding provider startup imports (openclaw#47467)

* Scope Control UI sessions per gateway (openclaw#47453)

* Scope Control UI sessions per gateway

Signed-off-by: sallyom <somalley@redhat.com>

* Add changelog for Control UI session scoping

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>

* Gateway: scrub credentials from endpoint snapshots (openclaw#46799)

* Gateway: scrub credentials from endpoint snapshots

* Gateway: scrub raw endpoint credentials in snapshots

* Gateway: preserve config redaction round-trips

* Gateway: restore redacted endpoint URLs on apply

* fix(config): avoid failing startup on implicit memory slot (openclaw#47494)

* fix(config): avoid failing on implicit memory slot

* fix(config): satisfy build for memory slot guard

* docs(changelog): note implicit memory slot startup fix (openclaw#47494)

* CLI: lazy-load auth choice provider fallback (openclaw#47495)

* CLI: lazy-load auth choice provider fallback

* CLI: cover lazy auth choice provider fallback

* fix(ci): config drift found and documented

* Gateway: tighten forwarded client and pairing guards (openclaw#46800)

* Gateway: tighten forwarded client and pairing guards

* Gateway: make device approval scope checks atomic

* Gateway: preserve device approval baseDir compatibility

* Changelog: note CLI OOM startup fixes (openclaw#47525)

* Commands: lazy-load model picker provider runtime (openclaw#47536)

* Commands: lazy-load model picker provider runtime

* Tests: cover model picker runtime boundary

* docs: fork rebase spec + per-patch diffs for upstream v2026.3.13 merge

Generated after failed merge attempt (2026-03-15). Contains:
- FORK-PATCHES-SPEC.md: implementation instructions per patch group (249 lines)
- FORK-REBASE-SPEC.md: technical context, errors, SSE protocol (292 lines)
- fork-patches/by-patch/: 31 per-patch git diffs (consultable on demand)
- fork-patches/fork-vs-upstream-src-only.patch: full squashed diff (5813 lines)

Co-authored-by: Bob

* docs: add merge plan from feat/upstream-merge-3.13 branch

Co-authored-by: Bob

* docs: remove old merge plan — superseded by FORK-PATCHES-SPEC + FORK-REBASE-SPEC

Co-authored-by: Bob

* chore(fmt): format changes and broken types

* Commands: split static onboard auth choice help (openclaw#47545)

* Commands: split static onboard auth choice help

* Tests: cover static onboard auth choice help

* Changelog: note static onboard auth choice help

* CLI/completion: fix generator OOM and harden plugin registries (openclaw#45537)

* fix: avoid OOM during completion script generation

* CLI/completion: fix PowerShell nested command paths

* CLI/completion: cover generated shell scripts

* Changelog: note completion generator follow-up

* Plugins: reserve shared registry names

---------

Co-authored-by: Xiaoyi <xiaoyi@example.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* fix(plugins): load bundled extensions from dist (openclaw#47560)

* fix(models): preserve stream usage compat opt-ins (openclaw#45733)

Preserves explicit `supportsUsageInStreaming` overrides from built-in provider
catalogs and user config instead of unconditionally forcing `false` on non-native
openai-completions endpoints.

Adds `applyNativeStreamingUsageCompat()` to set `supportsUsageInStreaming: true`
on ModelStudio (DashScope) and Moonshot models at config build time so their
native streaming usage works out of the box.

Closes openclaw#46142

Co-authored-by: pezy <peizhe.chen@vbot.cn>

* Plugins: reserve context engine ownership

* docs(zalo): document current Marketplace bot behavior (openclaw#47552)

Verified:
- pnpm check:docs

Co-authored-by: Tomáš Dinh <82420070+No898@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* Docs: move release runbook to maintainer repo (openclaw#47532)

* Docs: redact private release setup

* Docs: tighten release order

* Docs: move release runbook to maintainer repo

* Docs: delete public mac release page

* Docs: remove zh-CN mac release page

* Docs: turn release checklist into release policy

* Docs: point release policy to private docs

* Docs: regenerate zh-CN release policy pages

* Docs: preserve Doctor in zh-CN hubs

* Docs: fix zh-CN polls label

* Docs: tighten docs i18n term guardrails

* Docs: enforce zh-CN glossary coverage

* Scripts: rebuild on extension and tsdown config changes (openclaw#47571)

Merged via squash.

Prepared head SHA: edd8ed8
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* fix: reset chat buffer on tool-start to prevent intermediary text accumulation

The Pi SDK resets lastStreamedAssistantCleaned between tool calls, but
the gateway chatRunState.buffers was not reset — causing mergedText to
accumulate text from ALL prior turns. The SSE subscriber (which resets
lastTextLen=0 on tool-start) then re-emitted the entire conversation.

Co-authored-by: Bob

* fix(release): block oversized npm packs that regress low-memory startup (openclaw#46850)

* fix(release): guard npm pack size regressions

* fix(release): fail closed when npm omits pack size

* Plugins: reserve context engine ownership (openclaw#47595)

* Plugins: reserve context engine ownership

* Update src/context-engine/registry.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix: restore Patch #4 (chat mirror) and Patch #5 (inbound push) lost in Codex merge

Patch #4: webchat-originated replies on WA-scoped sessions now mirror
to WhatsApp via sendMessageWhatsApp(). The Codex merge kept the
runContext.mirror registration but lost the delivery block.

Patch #5: inbound messages (WA/Slack/etc.) now broadcast to WS/SSE
clients via message.inbound event, restoring real-time cross-channel
message display in the webchat dashboard.

Co-authored-by: Bob

* fix: mirror delivery in emitChatFinal (embedded runner path)

The previous commit placed mirror only in the !agentRunStarted branch
of server-methods/chat.ts, but the embedded runner sets agentRunStarted=true
and delivers via emitChatFinal in server-chat.ts instead. This restores
the mirror block in the correct location — matching the alpha.

Co-authored-by: Bob

* Gateway: sync runtime post-build artifacts

* Plugins: harden context engine ownership

* fix: complete Patch #5 inbound push + fix mirror static import

- Add emitInboundMessageEvent() call in dispatch-from-config.ts (was
  only defined but never called — WA messages never reached SSE/webchat)
- Switch mirror from dynamic import() to static import (dynamic import
  failed silently in bundled build)

Co-authored-by: Bob

* fix: globalThis singleton for WA listeners to survive chunk duplication

The bundler splits active-listener.ts into a different chunk than
server-chat.ts (mirror) and auto-reply/monitor.ts (listener registration).
Static/dynamic imports resolve to different module instances, so mirror
always sees an empty listeners Map. Using globalThis ensures all chunks
share the same Map instance.

Co-authored-by: Bob

* fix(plugins): fix bundled plugin roots and skill assets (openclaw#47601)

* fix(acpx): resolve bundled plugin root correctly

* fix(plugins): copy bundled plugin skill assets

* fix(plugins): tolerate missing bundled skill paths

* chore: remove temporary mirror debug logs

Co-authored-by: Bob

* fix: globalThis singleton for inbound event listeners (chunk duplication)

Same root cause as the WA listener fix: dispatch-from-config.ts emits
inbound events in one chunk, server.impl.ts subscribes in another.
Module-level Set gets duplicated across chunks.

Co-authored-by: Bob

* fix: emit inbound events from WA process-message path (not dispatch-from-config)

dispatch-from-config.ts is NOT in the WA message processing chain.
WA messages go through process-message.ts → provider-dispatcher.ts.
Moved emitInboundMessageEvent to process-message.ts where WA messages
are actually processed.

Co-authored-by: Bob

* fix(ci): restore config baseline release-check output (openclaw#47629)

* Docs: regenerate config baseline

* Chore: ignore generated config baseline

* Update .prettierignore

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* CLI: support package-manager installs from GitHub main (openclaw#47630)

* CLI: resolve package-manager main install specs

* CLI: skip registry resolution for raw package specs

* CLI: support main package target updates

* CLI: document package update specs in help

* Tests: cover package install spec resolution

* Tests: cover npm main-package updates

* Tests: cover update --tag main

* Installer: support main package targets

* Installer: support main package targets on Windows

* Docs: document package-manager main updates

* Docs: document installer main targets

* Docs: document npm and pnpm main installs

* Docs: document update --tag main

* Changelog: note package-manager main installs

* Update src/infra/update-global.test.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix: emit message.inbound directly on gatewayEventBus

Bypass inbound-events.ts entirely — its module-level Set suffers from
chunk duplication even with globalThis (timing/ordering issues).
gatewayEventBus already uses globalThis singleton and is proven to work
for chat/agent events. SSE listens on gatewayEventBus for message.inbound.

Co-authored-by: Bob

* fix(dev): align gateway watch with tsdown wrapper (openclaw#47636)

* Commands: lazy-load non-interactive plugin provider runtime (openclaw#47593)

* Commands: lazy-load non-interactive plugin provider runtime

* Tests: cover non-interactive plugin provider ordering

* Update src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.runtime.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Plugins: relocate bundled skill assets

* Plugins: skip nested node_modules in bundled skills

* Plugins: clean stale bundled skill outputs

* feat(plugins): move provider runtimes into bundled plugins

* build(plugins): add bundled provider plugin manifests

* Channels: move onboarding adapters into extensions

* Channels: use owned helper imports

* Plugins: broaden plugin surface for Codex App Server (openclaw#45318)

* Plugins: add inbound claim and Telegram interaction seams

* Plugins: add Discord interaction surface

* Chore: fix formatting after plugin rebase

* fix(hooks): preserve observers after inbound claim

* test(hooks): cover claimed inbound observer delivery

* fix(plugins): harden typing lease refreshes

* fix(discord): pass real auth to plugin interactions

* fix(plugins): remove raw session binding runtime exposure

* fix(plugins): tighten interactive callback handling

* Plugins: gate conversation binding with approvals

* Plugins: migrate legacy plugin binding records

* Plugins/phone-control: update test command context

* Plugins: migrate legacy binding ids

* Plugins: migrate legacy codex session bindings

* Discord: fix plugin interaction handling

* Discord: support direct plugin conversation binds

* Plugins: preserve Discord command bind targets

* Tests: fix plugin binding and interactive fallout

* Discord: stabilize directory lookup tests

* Discord: route bound DMs to plugins

* Discord: restore plugin bindings after restart

* Telegram: persist detached plugin bindings

* Plugins: limit binding APIs to Telegram and Discord

* Plugins: harden bound conversation routing

* Plugins: fix extension target imports

* Plugins: fix Telegram runtime extension imports

* Plugins: format rebased binding handlers

* Discord: bind group DM interactions by channel

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* feat(plugins): add compatible bundle support

* feat(plugins): move provider runtimes into bundled plugins

* build(plugins): add bundled provider plugin packages

* fix(plugins): restore provider compatibility fallbacks

* Changelog: note plugin agent integrations

* chore: remove inbound-push debug logs

Co-authored-by: Bob

* refactor: decouple channel setup discovery

* refactor: move telegram onboarding to setup wizard

* docs: describe channel setup wizard surface

* fix: tighten setup wizard typing

* fix: deduplicate inbound events + use raw body instead of envelope

- Remove emitInboundMessageEvent from dispatch-from-config.ts (WA uses
  process-message.ts path, causing double emit)
- Use params.msg.body (clean) instead of combinedBody (with envelope
  prefix) to avoid showing [WhatsApp ...] metadata in chat UI

Co-authored-by: Bob

* Commands: lazy-load auth choice plugin provider runtime (openclaw#47692)

* Commands: lazy-load auth choice plugin provider runtime

* Tests: cover auth choice plugin provider runtime

* refactor: expand setup wizard flow

* refactor: move discord and slack to setup wizard

* refactor: drop onboarding adapter sdk exports

* docs: update setup wizard capabilities

* feat(plugins): test bundle MCP end to end

* fix(onboarding): use scoped plugin snapshots to prevent OOM on low-memory hosts (openclaw#46763)

* fix(onboarding): use scoped plugin snapshots to prevent OOM on low-memory hosts

Onboarding and channel-add flows previously loaded the full plugin registry,
which caused OOM crashes on memory-constrained hosts. This patch introduces
scoped, non-activating plugin registry snapshots that load only the selected
channel plugin without replacing the running gateway's global state.

Key changes:
- Add onlyPluginIds and activate options to loadOpenClawPlugins for scoped loads
- Add suppressGlobalCommands to plugin registry to avoid leaking commands
- Replace full registry reloads in onboarding with per-channel scoped snapshots
- Validate command definitions in snapshot loads without writing global registry
- Preload configured external plugins via scoped discovery during onboarding

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(test): add return type annotation to hoisted mock to resolve TS2322

* fix(plugins): enforce cache:false invariant for non-activating snapshot loads

* Channels: preserve lazy scoped snapshot import after rebase

* Onboarding: scope channel snapshots by plugin id

* Catalog: trust manifest ids for channel plugin mapping

* Onboarding: preserve scoped setup channel loading

* Onboarding: restore built-in adapter fallback

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>

* feat(plugins): add provider usage runtime hooks

* feat(plugins): move bundled providers behind plugin hooks

* docs(plugins): document provider runtime usage hooks

* docs(plugins): unify bundle format explainer

* fix: repair onboarding adapter registry imports

* refactor: expand setup wizard input flow

* refactor: move signal imessage mattermost to setup wizard

* docs: document richer setup wizard prompts

* feat(plugins): move anthropic and openai vendors to plugins

* fix: repair onboarding setup-wizard imports

* test(discord): cover startup phase logging

* fix: reduce plugin and discord warning noise

* chore: raise plugin registry cache cap

* build: suppress protobufjs eval warning in tsdown

* refactor: tighten setup wizard onboarding bridge

* refactor: move bluebubbles to setup wizard

* refactor: move nextcloud talk to setup wizard

* CLI: restore lightweight root help and scoped status plugin preload

* Matrix: lazy-load runtime-heavy channel paths

* CI: add CLI startup memory regression check

* MSTeams: lazy-load runtime-heavy channel paths

* refactor: expand setup wizard flow

* refactor: move whatsapp to setup wizard

* refactor: move irc to setup wizard

* refactor: move tlon to setup wizard

* refactor: move googlechat to setup wizard

* refactor: expose setup wizard sdk surfaces

* Feishu: lazy-load runtime-heavy channel paths

* Google Chat: lazy-load runtime-heavy channel paths

* fix: gate setup-only plugin side effects

* feat(web-search): add plugin-backed search providers

* fix(web-search): restore build after plugin rebase

* refactor(web-search): move providers into company plugins

* WhatsApp: lazy-load setup wizard surface

* fix: align channel adapters with plugin sdk

* fix: repair node24 ci type drift

* refactor(google): merge gemini auth into google plugin

* feat(plugins): merge openai vendor seams into one plugin

* refactor(plugins): lazy load provider runtime shims

* perf(cli): trim help startup imports

* perf(status): defer heavy startup loading

* fix(matrix): assert outbound runtime hooks

* refactor: extend setup wizard account resolution

* refactor: move feishu zalo zalouser to setup wizard

* refactor: move matrix msteams twitch to setup wizard

* refactor: drop channel onboarding fallback

* fix: quiet discord startup logs

* Slack: lazy-load setup wizard surface

* Feishu: drop stale runtime onboarding export

* Discord: lazy-load setup wizard surface

* Signal: lazy-load setup wizard surface

* perf(plugins): lazy-load setup surfaces

* fix(cli): repair preaction merge typo

* Signal: restore setup surface helper exports

* iMessage: lazy-load setup wizard surface

* Nextcloud Talk: split setup adapter helpers

* fix: remove stale dist plugin dirs

* BlueBubbles: split setup adapter helpers

* test(plugins): cover retired google auth compatibility

* refactor(tests): share plugin registration helpers

* refactor(plugins): share bundled compat transforms

* refactor(google): split oauth flow modules

* refactor(plugin-sdk): centralize entrypoint manifest

* fix(docs): harden i18n prompt failures

* docs(i18n): sync zh-CN google plugin references

* fix(docs): run i18n through a local rpc client

* build(plugin-sdk): enforce export sync in check

* docs(google): remove stale plugin references

* IRC: split setup adapter helpers

* refactor: move line to setup wizard

* refactor: trim onboarding sdk exports

* Telegram: split setup adapter helpers

* fix: allow plugin package id hints

* Tlon: split setup adapter helpers

* LINE: split setup adapter helpers

* fix: restore ci type checks

* fix: resolve line setup rebase drift

* Mattermost: split setup adapter helpers

* refactor: merge minimax bundled plugins

* docs: refresh zh-CN model providers

* perf(plugins): lazy-load channel setup entrypoints

* Google Chat: split setup adapter helpers

* Matrix: split setup adapter helpers

* MSTeams: split setup adapter helpers

* feat(telegram): add topic-edit action

* fix(telegram): normalize topic-edit targets

* fix: add Telegram topic-edit action (openclaw#47798)

* Feishu: split setup adapter helpers

* fixed main?

* Zalo: split setup adapter helpers

* refactor(plugins): split lightweight channel setup modules

* Zalouser: split setup adapter helpers

* Status: skip unused channel issue scan in JSON mode

* fix(plugins): tighten lazy setup typing

* fix: tighten outbound channel/plugin resolution

* fix(ci): repair security and route test fixtures

* secrets: harden read-only SecretRef command paths and diagnostics (openclaw#47794)

* secrets: harden read-only SecretRef resolution for status and audit

* CLI: add SecretRef degrade-safe regression coverage

* Docs: align SecretRef status and daemon probe semantics

* Security audit: close SecretRef review gaps

* Security audit: preserve source auth SecretRef configuredness

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

* Gateway: add presence-only probe mode for status

* refactor: move group access into setup wizard

* feat: add nostr setup and unify channel setup discovery

* fix: drop duplicate channel setup import

* feat: add openshell sandbox backend

* feat(system-prompt): replace hardcoded identity with butley-system-prompt.md

Cherry-picked from work (9e1137a). Guilherme's PR #4.
Co-authored-by: Bob

* fix: suppress SSE finalization on retryable rate-limit errors (openclaw#32)

Cherry-picked from work (456f091). Retryable provider errors (429,
overload) no longer kill the SSE stream — keeps it open during
gateway retries/failover so text flows when retry succeeds.

Co-authored-by: Bob

* Status: scope JSON plugin preload to configured channels

* feat: persist previousSessionId chain across session resets (openclaw#34)

Cherry-picked from work (b465233). Adapted: upstream already has
previousSessionEntry — only added previousSessionIdForChain for
fallback chain persistence on reset/new/idle-expiry.

Co-authored-by: Bob

* Status: lazy-load read-only account inspectors

* refactor(core): land plugin auth and startup cleanup

* chore: restore butley-api + clickup-api custom plugins from alpha

These custom extensions were missing from the rebase branch.
Copied from alpha verbatim.

Co-authored-by: Bob

* CLI: route gateway status before program registration

* feat: add remote openshell sandbox mode

* docs: expand openshell sandbox docs

* feat: add firecrawl onboarding search plugin

* Gateway: lazy-load SSH status helpers

* refactor: rename channel setup flow seam

* refactor: move setup fallback into setup registry

* feat: add synology chat setup wizard

* build: add setup entrypoints for migrated channel plugins

* docs: update channel setup docs

* fix: update feishu setup adapter import

* Status: lazy-load channel summary helpers

* Agents: skip eager context warmup for status commands

* Status: route JSON through lean command

* refactor(plugins): move auth and model policy to providers

* fix: control UI sends correct provider prefix when switching models

The model selector was using just the model ID (e.g. "gpt-5.2") as the
option value. When sent to sessions.patch, the server would fall back to
the session's current provider ("anthropic") yielding "anthropic/gpt-5.2"
instead of "openai/gpt-5.2".

Now option values use "provider/model" format, and resolveModelOverrideValue
and resolveDefaultModelValue also return the full provider-prefixed key so
selected state stays consistent.

* fix: format default model label as 'model · provider' for consistency

The default option showed 'Default (openai/gpt-5.2)' while individual
options used the friendlier 'gpt-5.2 · openai' format.

* Nostr: break setup-surface import cycle

* Tests: stabilize bundle MCP env on Windows

* Status: lazy-load channel security and summaries

* Docs: refresh generated config baseline

* test: silence vitest warning noise

* Status: lazy-load text scan helpers

* refactor: rename setup helper surfaces

* test: fix fetch mock typing

* docs: update channel setup wording

* Security: lazy-load channel audit provider helpers

* fix(ui): centralize control model ref handling

* CLI: route gateway status through daemon status

* Status: restore lazy scan runtime typing

* feat: token usage tracking via llm_output hook

* fix: remove duplicate previousSessionEntry declaration

* fix: butley-api extension imports — use openclaw/plugin-sdk instead of relative source paths

* fix: ensure llm_output hook is included in butley-api build output

- Add optional bundled cluster filtering to listBundledPluginBuildEntries()
  to skip extensions with native dependencies (matrix, whatsapp, etc.)
  that cannot be bundled by rolldown on all platforms
- Filter plugin-sdk entries for optional clusters to prevent native
  .node binary bundling failures
- Matches upstream's shouldBuildBundledCluster() pattern
- butley-api dist output now correctly contains both the tool
  registration AND the llm_output hook in the register() default export

* Revert "fix: ensure llm_output hook is included in butley-api build output"

This reverts commit 4558c9d.

* fix: revert tsdown changes, copy butley-api to dist via Dockerfile

* feat: [FORK-PATCH-37] token usage tracking via direct Convex POST

* fix: improve FORK-PATCH-37 logging and add cache token tracking

---------

Signed-off-by: sallyom <somalley@redhat.com>
Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
Co-authored-by: Jason <jseppa01@gmail.com>
Co-authored-by: Jason Separovic <jason@wilma.dog>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Praveen K  Singh <pr4veensingh@proton.me>
Co-authored-by: webdevpraveen <webdevpraveen@users.noreply.github.com>
Co-authored-by: SkunkWorks0x <1imaniww@gmail.com>
Co-authored-by: imanisynapse <imanisynapse@gmail.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: Sahan <57447079+sahancava@users.noreply.github.com>
Co-authored-by: frankekn <4488090+frankekn@users.noreply.github.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: thepagent <262003297+thepagent@users.noreply.github.com>
Co-authored-by: Ace Lee <416815882@qq.com>
Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ted Li <tl2493@columbia.edu>
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: 助爪 <xaeon2026@gmail.com>
Co-authored-by: xaeon2026 <264572156+xaeon2026@users.noreply.github.com>
Co-authored-by: Andrew Demczuk <andrew.demczuk@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Harold Hunt <harold@pwrdrvr.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Aditya Chaudhary <55331140+ItsAditya-xyz@users.noreply.github.com>
Co-authored-by: Sally O'Malley <somalley@redhat.com>
Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
Co-authored-by: Lucas Machado <lucasmpr@me.com>
Co-authored-by: xiaoyi <yishanxin1991@gmail.com>
Co-authored-by: Xiaoyi <xiaoyi@example.com>
Co-authored-by: peizhe.chen <peizhe.chen@vbot.cn>
Co-authored-by: Tomáš Dinh <82420070+No898@users.noreply.github.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Mason <bwjava@163.com>
Co-authored-by: Josh Avant <830519+joshavant@users.noreply.github.com>
Co-authored-by: Christopher Chamaletsos <webmaster@kantoliana.gr>
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 23, 2026
…nclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.

(cherry picked from commit 26e0a3e)
alexey-pelykh added a commit to remoteclaw/remoteclaw that referenced this pull request Mar 23, 2026
* fix(gateway): skip device pairing when auth.mode=none

Fixes openclaw#42931

When gateway.auth.mode is set to "none", authentication succeeds with
method "none" but sharedAuthOk remains false because the auth-context
only recognises token/password/trusted-proxy methods. This causes all
pairing-skip conditions to fail, so Control UI browser connections get
closed with code 1008 "pairing required" despite auth being disabled.

Short-circuit the skipPairing check: if the operator explicitly
disabled authentication, device pairing (which is itself an auth
mechanism) must also be bypassed.

Fixes openclaw#42931

(cherry picked from commit 9bffa34)

* fix(gateway): strip unbound scopes for shared-auth connects

(cherry picked from commit 7dc447f)

* fix(gateway): increase WS handshake timeout from 3s to 10s (openclaw#49262)

* fix(gateway): increase WS handshake timeout from 3s to 10s

The 3-second default is too aggressive when the event loop is under load
(concurrent sessions, compaction, agent turns), causing spurious
'gateway closed (1000)' errors on CLI commands like `openclaw cron list`.

Changes:
- Increase DEFAULT_HANDSHAKE_TIMEOUT_MS from 3_000 to 10_000
- Add OPENCLAW_HANDSHAKE_TIMEOUT_MS env var for user override (no VITEST gate)
- Keep OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS as fallback for existing tests

Fixes openclaw#46892

* fix: restore VITEST guard on test env var, use || for empty-string fallback, fix formatting

* fix: cover gateway handshake timeout env override (openclaw#49262) (thanks @fuller-stack-dev)

---------

Co-authored-by: Wilfred <wilfred@Wilfreds-Mac-mini.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit 36f394c)

* fix(gateway): skip Control UI pairing when auth.mode=none (closes openclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.

(cherry picked from commit 26e0a3e)

* fix(gateway): clear trusted-proxy control ui scopes

(cherry picked from commit ccf16cd)

* fix(gateway): guard interface discovery failures

Closes openclaw#44180.
Refs openclaw#47590.
Co-authored-by: Peter Steinberger <steipete@gmail.com>

(cherry picked from commit 3faaf89)

* fix(gateway): suppress ciao interface assertions

Closes openclaw#38628.
Refs openclaw#47159, openclaw#52431.
Co-authored-by: Peter Steinberger <steipete@gmail.com>

(cherry picked from commit c0d4abc)

* fix(gateway): run before_tool_call for HTTP tools

(cherry picked from commit 8cc0c9b)

* fix(gateway): skip seq-gap broadcast for stale post-lifecycle events (openclaw#43751)

* fix: stop stale gateway seq-gap errors (openclaw#43751) (thanks @caesargattuso)

* fix: keep agent.request run ids session-scoped

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
(cherry picked from commit 57f1cf6)

* fix(gateway): honor trusted proxy hook auth rate limits

(cherry picked from commit 4da617e)

* fix(gateway): enforce browser origin check regardless of proxy headers

In trusted-proxy mode, enforceOriginCheckForAnyClient was set to false
whenever proxy headers were present. This allowed browser-originated
WebSocket connections from untrusted origins to bypass origin validation
entirely, as the check only ran for control-ui and webchat client types.

An attacker serving a page from an untrusted origin could connect through
a trusted reverse proxy, inherit proxy-injected identity, and obtain
operator.admin access via the sharedAuthOk / roleCanSkipDeviceIdentity
path without any origin restriction.

Remove the hasProxyHeaders exemption so origin validation runs for all
browser-originated connections regardless of how the request arrived.

Fixes GHSA-5wcw-8jjv-m286

(cherry picked from commit ebed3bb)

* fix(gateway): harden health monitor account gating (openclaw#46749)

* gateway: harden health monitor account gating

* gateway: tighten health monitor account-id guard

(cherry picked from commit 29fec8b)

* fix(gateway): bound unanswered client requests (openclaw#45689)

* fix(gateway): bound unanswered client requests

* fix(gateway): skip default timeout for expectFinal requests

* fix(gateway): preserve gateway call timeouts

* fix(gateway): localize request timeout policy

* fix(gateway): clamp explicit request timeouts

* fix(gateway): clamp default request timeout

(cherry picked from commit 5fc43ff)

* fix(gateway): propagate real gateway client into plugin subagent runtime

Plugin subagent dispatch used a hardcoded synthetic client carrying
operator.admin, operator.approvals, and operator.pairing for all
runtime.subagent.* calls. Plugin HTTP routes with auth:"plugin" require
no gateway auth by design, so an unauthenticated external request could
drive admin-only gateway methods (sessions.delete, agent.run) through
the subagent runtime.

Propagate the real gateway client into the plugin runtime request scope
when one is available. Plugin HTTP routes now run inside a scoped
runtime client: auth:"plugin" routes receive a non-admin synthetic
operator.write client; gateway-authenticated routes retain admin-capable
scopes. The security boundary is enforced at the HTTP handler level.

Fixes GHSA-xw77-45gv-p728

(cherry picked from commit a1520d7)

* fix(gateway): enforce caller-scope subsetting in device.token.rotate

device.token.rotate accepted attacker-controlled scopes and forwarded
them to rotateDeviceToken without verifying the caller held those
scopes. A pairing-scoped token could rotate up to operator.admin on
any already-paired device whose approvedScopes included admin.

Add a caller-scope subsetting check before rotateDeviceToken: the
requested scopes must be a subset of client.connect.scopes via the
existing roleScopesAllow helper. Reject with missing scope: <scope>
if not.

Also add server.device-token-rotate-authz.test.ts covering both the
priv-esc path and the admin-to-node-invoke chain.

Fixes GHSA-4jpw-hj22-2xmc

(cherry picked from commit dafd61b)

* fix(gateway): pin plugin webhook route registry (openclaw#47902)

(cherry picked from commit a69f619)

* fix(gateway): split conversation reset from admin reset

(cherry picked from commit c91d162)

* fix(gateway): harden token fallback/reconnect behavior and docs (openclaw#42507)

* fix(gateway): harden token fallback and auth reconnect handling

* docs(gateway): clarify auth retry and token-drift recovery

* fix(gateway): tighten auth reconnect gating across clients

* fix: harden gateway token retry (openclaw#42507) (thanks @joshavant)

(cherry picked from commit a76e810)

* fix: adapt cherry-picks for fork TS strictness

- Replace OpenClawConfig with RemoteClawConfig in server-channels and
  server-runtime-state
- Replace loadOpenClawPlugins with loadRemoteClawPlugins in server-plugins
  and remove unsupported runtimeOptions field and dead subagent runtime code
- Export HookClientIpConfig type from server-http and thread it through
  server/hooks into server-runtime-state and server.impl
- Create plugins-http/ submodules (path-context, route-match, route-auth)
  extracted from the monolithic plugins-http.ts by upstream refactor
- Create stub modules for gutted upstream layers: acp/control-plane/manager,
  agents/bootstrap-cache, agents/pi-embedded, agents/internal-events
- Strip thinkingLevel, reasoningLevel, skillsSnapshot from SessionEntry
  literals in agent.ts and session-reset-service.ts (Pi-specific fields)
- Remove internalEvents from agent ingress opts and loadGatewayModelCatalog
  from sessions patch call (not present in fork types)
- Fix connect-policy tests to pass booleans instead of role strings for
  the sharedAuthOk parameter (fork changed the function signature)
- Add isHealthMonitorEnabled to ChannelManager mocks in test files
- Widen runBeforeToolCallHook mock return type to accept blocked: true
- Add explicit string types for msg params in server-plugins logger

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: apply fork naming to cherry-picked bonjour files

---------

Co-authored-by: Andrew Demczuk <andrew.demczuk@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: fuller-stack-dev <fullerstackd@gmail.com>
Co-authored-by: Wilfred <wilfred@Wilfreds-Mac-mini.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: caesargattuso <goojjwgle@gmail.com>
Co-authored-by: Robin Waslander <r.waslander@gmail.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Josh Avant <830519+joshavant@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 24, 2026
…nclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.

(cherry picked from commit 26e0a3e)
alexey-pelykh added a commit to remoteclaw/remoteclaw that referenced this pull request Mar 24, 2026
…1958)

* refactor: share boundary open and gateway test helpers

(cherry picked from commit 100d9a7)

* Gateway: defer full channel plugins until after listen

(cherry picked from commit 1b234b9)

* perf: trim more vitest thread pins

(cherry picked from commit 274af04)

* refactor(gateway): share interface discovery helpers

(cherry picked from commit 31ee442)

* Fix Control UI operator.read scope handling (openclaw#53110)

Preserve Control UI scopes through the device-auth bypass path, normalize implied operator device-auth scopes, ignore cached under-scoped operator tokens, and degrade read-backed main pages gracefully when a connection truly lacks operator.read.

Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
(cherry picked from commit 3e2b3bd)

* refactor: share chat abort test helpers

(cherry picked from commit 8de94ab)

* Gateway: gate deferred channel startup behind opt-in

(cherry picked from commit 96ed010)

* test: honor env auth in gateway live probes

(cherry picked from commit d2a1b24)

* fix(gateway): require auth for canvas routes

(cherry picked from commit d5dc6b6)

* test: refine gateway auth helper coverage

(cherry picked from commit e25fa44)

* test: align chat abort helpers with gateway handler types

(cherry picked from commit ee1d4eb)

* refactor: extract websocket handshake auth helpers

(cherry picked from commit 01e4845)

* test: simplify trusted proxy coverage

(cherry picked from commit 118abfb)

* Gateway tests: share ordered client teardown helper

(cherry picked from commit 13894ec)

* test: share gateway chat history setup

(cherry picked from commit 1886fe5)

* refactor: share gateway session store migration

(cherry picked from commit 198c248)

* refactor(gateway): move request client ip resolution to net

(cherry picked from commit 1d986f1)

* test: simplify runtime config coverage

(cherry picked from commit 1f85c9a)

* refactor(test): share hook request handler fixtures

(cherry picked from commit 268e036)

* fix(gateway): skip Control UI pairing when auth.mode=none (closes openclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.

(cherry picked from commit 26e0a3e)

* test: simplify auth rate limit coverage

(cherry picked from commit 29b9e21)

* refactor: share plugin route auth test harness

(cherry picked from commit 2f58647)

* fix(session): preserve `lastAccountId` and `lastThreadId` on session reset

(cherry picked from commit 3066607)

* refactor: share agent wait dedupe test entries

(cherry picked from commit 31c8bb9)

* test: stabilize gateway thread harness

(cherry picked from commit 383c61e)

* refactor(device): share missing-scope helper

(cherry picked from commit 43838b1)

* refactor(gateway): cache hook proxy config in runtime state

(cherry picked from commit 445ff02)

* fix: preserve loopback gateway scopes for local auth

(cherry picked from commit 4ab016a)

* feat(gateway): add talk speak rpc

(cherry picked from commit 4ac355b)

* test: tighten gateway helper coverage

(cherry picked from commit 4aec20d)

* fix(gateway): require admin for agent session reset

(cherry picked from commit 50f6a2f)

* fix: sweep stale chatRunState buffers for stuck runs

emitChatFinal frees buffers on clean run completion, and the
maintenance timer sweeps abortedRuns after ABORTED_RUN_TTL_MS. But
runs that get stuck (e.g. LLM timeout without triggering clean
lifecycle end) are never aborted and their string buffers persist
indefinitely. This is the direct trigger for the StringAdd_CheckNone
OOM crash reported in the issue.

Add a stale buffer sweep in the maintenance timer that cleans up
buffers, deltaSentAt, and deltaLastBroadcastLen for any run not
updated within ABORTED_RUN_TTL_MS, regardless of abort status.

Closes openclaw#51821

(cherry picked from commit 550deb8)

* fix: stop main-session UI replies inheriting channel routes

(cherry picked from commit 5acf6ca)

* fix(sessions): create transcript file on chat.inject when missing (openclaw#36645)

`chat.inject` called `appendAssistantTranscriptMessage` with
`createIfMissing: false`, causing a hard error when the transcript
file did not exist on disk despite having a valid `transcriptPath`
in session metadata. This commonly happens with ACP oneshot/run
sessions where the session entry is created but the transcript file
is not yet materialized.

The fix is a one-character change: `createIfMissing: true`. The
`ensureTranscriptFile` helper already handles directory creation
and file initialization safely.

Fixes openclaw#36170

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
(cherry picked from commit 5c73ed6)

* test: share channel health helpers

(cherry picked from commit 5eaa146)

* fix: adapt cherry-picks for fork TS strictness

- Add stubs for upstream-only modules (channel-plugin-ids, provider-registry, synthesizeSpeech)
- Fix OpenClawConfig → RemoteClawConfig in talk.ts
- Add resolveHookClientIpConfig export to hooks.ts
- Add missing TtsConfig fields (microsoft, openai.speed/instructions)
- Fix chatThinkingLevel as optional on ChatState
- Fix test type annotations (dedupe Map, connect-policy booleans, abort helpers)
- Add missing imports (getReplyFromConfig, GatewayRequestError, startConnect helper)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: preserve raw token scopes on repair approval without re-normalizing

When a device is re-paired without explicitly requested scopes, reuse
the existing token scopes as-is instead of re-normalizing (which expands
operator.admin to include operator.read/write).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Andrew Demczuk <andrew.demczuk@gmail.com>
Co-authored-by: Jealous <CooLanfei@163.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Karan Uppal <karan.uppal711@gmail.com>
Co-authored-by: 2233admin <57929895+2233admin@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 24, 2026
The revert of openclaw#43478 (commit 39b4185) was silently undone by
3704293 which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.

(cherry picked from commit 92fc806)
alexey-pelykh added a commit to remoteclaw/remoteclaw that referenced this pull request Mar 24, 2026
…1959)

* refactor: share gateway client auth retry helpers

(cherry picked from commit 5f34391)

* Gateway: preserve discovered session store paths

(cherry picked from commit 60c1577)

* refactor: share node pending test client

(cherry picked from commit 644fb76)

* fix: add null guards to usage sort comparators

Prevents crash when totals is undefined in byModel/byProvider/byAgent
sort comparators. Fixes 'Cannot read properties of undefined (reading
totalTokens)' crash that causes context overflow in active sessions.

(cherry picked from commit 6921716)

* refactor: share gateway credential secretref assertions

(cherry picked from commit 6cc86ad)

* fix: force-stop lingering gateway client sockets

(cherry picked from commit 727fc79)

* Gateway: lazily resolve channel runtime

(cherry picked from commit 776e5d8)

* test: stabilize gateway alias coverage

(cherry picked from commit 7b00a06)

* Gateway: preserve trusted-proxy browser scopes

(cherry picked from commit 8661c27)

* test: simplify control ui http coverage

(cherry picked from commit 91d4f5c)

* test: tighten server method helper coverage

(cherry picked from commit 91f1894)

* fix(gateway): remove re-introduced auth.mode=none pairing bypass

The revert of openclaw#43478 (commit 39b4185) was silently undone by
3704293 which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.

(cherry picked from commit 92fc806)

* fix(gateway): avoid probe false negatives after connect

(cherry picked from commit 93df5f6)

* fix(gateway): skip device pairing when auth.mode=none

Fixes openclaw#42931

When gateway.auth.mode is set to "none", authentication succeeds with
method "none" but sharedAuthOk remains false because the auth-context
only recognises token/password/trusted-proxy methods. This causes all
pairing-skip conditions to fail, so Control UI browser connections get
closed with code 1008 "pairing required" despite auth being disabled.

Short-circuit the skipPairing check: if the operator explicitly
disabled authentication, device pairing (which is itself an auth
mechanism) must also be bypassed.

Fixes openclaw#42931

(cherry picked from commit 9bffa34)

* Gateway: cover lazy channel runtime resolution

(cherry picked from commit 9ee0fb5)

* fix(gateway): propagate real gateway client into plugin subagent runtime

Plugin subagent dispatch used a hardcoded synthetic client carrying
operator.admin, operator.approvals, and operator.pairing for all
runtime.subagent.* calls. Plugin HTTP routes with auth:"plugin" require
no gateway auth by design, so an unauthenticated external request could
drive admin-only gateway methods (sessions.delete, agent.run) through
the subagent runtime.

Propagate the real gateway client into the plugin runtime request scope
when one is available. Plugin HTTP routes now run inside a scoped
runtime client: auth:"plugin" routes receive a non-admin synthetic
operator.write client; gateway-authenticated routes retain admin-capable
scopes. The security boundary is enforced at the HTTP handler level.

Fixes GHSA-xw77-45gv-p728

(cherry picked from commit a1520d7)

* refactor: share control ui hardlink asset setup

(cherry picked from commit a3ece09)

* test(gateway): avoid hoisted reply mock tdz

(cherry picked from commit a60a4b4)

* fix(gateway): pin plugin webhook route registry (openclaw#47902)

(cherry picked from commit a69f619)

* test(gateway): stabilize suite session-store config (openclaw#52193)

* test(gateway): stabilize suite session-store config

* test(gateway): preserve seeded config semantics

* test(gateway): update seeded session store overrides

(cherry picked from commit ad24fcc)

* test: share plugin http auth helpers

(cherry picked from commit b644669)

* refactor: deduplicate push test fixtures

(cherry picked from commit b6b5e5c)

* test: share gateway reload helpers

(cherry picked from commit b72ac79)

* test: dedupe cron config setup

(cherry picked from commit ba34266)

* test: simplify talk config and path env coverage

(cherry picked from commit bec76be)

* refactor: share agent wait dedupe cleanup

(cherry picked from commit c889803)

* test(gateway): restore agent request route mock

(cherry picked from commit ccba943)

* fix: add gateway session reset routing coverage (openclaw#44773) (thanks @Lanfei)

(cherry picked from commit d40a4e3)

* fix(gateway): enforce caller-scope subsetting in device.token.rotate

device.token.rotate accepted attacker-controlled scopes and forwarded
them to rotateDeviceToken without verifying the caller held those
scopes. A pairing-scoped token could rotate up to operator.admin on
any already-paired device whose approvedScopes included admin.

Add a caller-scope subsetting check before rotateDeviceToken: the
requested scopes must be a subset of client.connect.scopes via the
existing roleScopesAllow helper. Reject with missing scope: <scope>
if not.

Also add server.device-token-rotate-authz.test.ts covering both the
priv-esc path and the admin-to-node-invoke chain.

Fixes GHSA-4jpw-hj22-2xmc

(cherry picked from commit dafd61b)

* refactor: share readiness test harness

(cherry picked from commit db9c755)

* test: simplify method scope coverage

(cherry picked from commit e1b9250)

* refactor: share node wake test apns fixtures

(cherry picked from commit e351a86)

* refactor: reuse gateway talk provider schema fields

(cherry picked from commit e94ac57)

* fix(gateway): enforce browser origin check regardless of proxy headers

In trusted-proxy mode, enforceOriginCheckForAnyClient was set to false
whenever proxy headers were present. This allowed browser-originated
WebSocket connections from untrusted origins to bypass origin validation
entirely, as the check only ran for control-ui and webchat client types.

An attacker serving a page from an untrusted origin could connect through
a trusted reverse proxy, inherit proxy-injected identity, and obtain
operator.admin access via the sharedAuthOk / roleCanSkipDeviceIdentity
path without any origin restriction.

Remove the hasProxyHeaders exemption so origin validation runs for all
browser-originated connections regardless of how the request arrived.

Fixes GHSA-5wcw-8jjv-m286

(cherry picked from commit ebed3bb)

* refactor(security): reuse hook agent routing normalization

(cherry picked from commit eece586)

* Hardening: tighten preauth WebSocket handshake limits (openclaw#44089)

* Gateway: tighten preauth handshake limits

* Changelog: note WebSocket preauth hardening

* Gateway: count preauth frame bytes accurately

* Gateway: cap WebSocket payloads before auth

(cherry picked from commit eff0d5a)

* test: share gateway chat run helpers

(cherry picked from commit f8efa30)

* refactor: share shared auth scope assertion

(cherry picked from commit feba7ea)

* fix: adapt cherry-picks for fork TS strictness and naming

* fix: resolve remaining TS errors from cherry-picks

* fix: revert push test to fork version — no relay transport in fork

* fix: remove duplicate function declarations in cron test

* fix: remove TalkSpeak schema refs removed by upstream refactoring

* fix: remove remaining TalkSpeak refs after upstream schema refactoring

* fix: add missing clampProbeTimeoutMs and fix testCase ref in control-ui test

* fix: resolve final TS type errors in cherry-picked tests

* fix: use InstanceType for GatewayClient type refs in test

* fix: resolve CI lint and test failures (no-explicit-any, hook audit finding ID)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
Co-authored-by: Stephen Schoettler <stephenschoettler@gmail.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Andrew Demczuk <andrew.demczuk@gmail.com>
Co-authored-by: Robin Waslander <r.waslander@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Luke <92253590+ImLukeF@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
sbezludny pushed a commit to sbezludny/openclaw that referenced this pull request Mar 27, 2026
The revert of openclaw#43478 (commit 39b4185) was silently undone by
3704293 which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
sbezludny pushed a commit to sbezludny/openclaw that referenced this pull request Mar 27, 2026
…nclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
The revert of openclaw#43478 (commit ca895ca) was silently undone by
cc4fb5e which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…nclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
The revert of openclaw#43478 (commit 4cf56b7) was silently undone by
9a2a84b which was based on a branch that included the original
change. This removes the auth.mode=none skipPairing condition again.

The blanket skip was too broad - it disabled pairing for ALL websocket
clients, not just Control UI behind reverse proxies.
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…nclaw#42931) (openclaw#47148)

When auth is completely disabled (mode=none), requiring device pairing
for Control UI operator sessions adds friction without security value
since any client can already connect without credentials.

Add authMode parameter to shouldSkipControlUiPairing so the bypass
fires only for Control UI + operator role + auth.mode=none. This avoids
the openclaw#43478 regression where a top-level OR disabled pairing for ALL
websocket clients.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime maintainer Maintainer-authored PR size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Remote Control UI over Tailscale stays stuck on "pairing required" despite gateway.auth.mode=none, trustedProxies, and gateway.mode=local

2 participants