Skip to content

Fix Control UI operator.read scope handling#53110

Merged
BunsDev merged 10 commits intomainfrom
ui/scope-patch-clean
Mar 23, 2026
Merged

Fix Control UI operator.read scope handling#53110
BunsDev merged 10 commits intomainfrom
ui/scope-patch-clean

Conversation

@BunsDev
Copy link
Copy Markdown
Member

@BunsDev BunsDev commented Mar 23, 2026

Summary

  • preserve requested Control UI scopes when dangerouslyDisableDeviceAuth: true bypasses device identity
  • expand implied operator device-auth scopes so cached/granted operator tokens reflect read/write capability correctly
  • ignore cached under-scoped operator device tokens in the browser client
  • degrade Control UI main pages gracefully when a connection truly lacks operator.read
  • add regression coverage for the bypass path, cached-token path, and chat-page fallback path

Why

The Control UI could fail with GatewayRequestError: missing scope: operator.read in a few related ways:

  1. Bypass regression — Control UI scopes were being cleared during the explicit dangerouslyDisableDeviceAuth: true bypass path.
  2. Literal cached/granted scopes — stored operator device-auth scopes were too literal, so admin/write capability did not consistently translate into read-backed UI behavior.
  3. Stale cached tokens — the browser client could silently reuse an older under-scoped operator device token.
  4. UI failure mode — when a connection really was under-scoped, main pages surfaced raw gateway errors instead of a clear degraded-state message.

This PR fixes the underlying scope path and the user-facing fallback behavior.

Files intentionally touched

Gateway / auth

  • src/gateway/server/ws-connection/message-handler.ts
  • src/gateway/server.auth.control-ui.suite.ts
  • src/shared/device-auth.ts
  • src/shared/device-auth.test.ts

Control UI

  • ui/src/ui/gateway.ts
  • ui/src/ui/gateway.node.test.ts
  • ui/src/ui/controllers/scope-errors.ts
  • ui/src/ui/controllers/chat.ts
  • ui/src/ui/controllers/chat.test.ts
  • ui/src/ui/controllers/presence.ts
  • ui/src/ui/controllers/sessions.ts
  • ui/src/ui/controllers/cron.ts
  • ui/src/ui/controllers/channels.ts
  • ui/src/ui/controllers/agents.ts
  • ui/src/ui/controllers/logs.ts
  • ui/src/ui/controllers/usage.ts

Validation

  • pnpm exec vitest run src/gateway/server.auth.control-ui.suite.ts src/shared/device-auth.test.ts ui/src/ui/gateway.node.test.ts ui/src/ui/controllers/chat.test.ts
  • pnpm build
  • pnpm check

Live verification

Verified locally against a patched repo gateway running on a separate dev port:

  • successful Control UI connection on the patched gateway
  • chat.history succeeded on the patched gateway
  • the prior raw missing scope: operator.read failure no longer reproduced on the patched path

@BunsDev BunsDev requested a review from a team as a code owner March 23, 2026 19:47
@openclaw-barnacle openclaw-barnacle bot added app: web-ui App: web-ui gateway Gateway runtime size: M labels Mar 23, 2026
@openclaw-barnacle openclaw-barnacle bot added the maintainer Maintainer-authored PR label Mar 23, 2026
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 23, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🟡 Medium Client-side DoS due to undefined variable in scope error detection
2 🟡 Medium Control UI scope retention without device identity when dangerouslyDisableDeviceAuth enabled

1. 🟡 Client-side DoS due to undefined variable in scope error detection

Property Value
Severity Medium
CWE CWE-248
Location ui/src/ui/controllers/scope-errors.ts:8-12

Description

The new isMissingOperatorReadScopeError helper references an undefined identifier detailCode, which will throw a ReferenceError at runtime when executed.

  • Trigger: any code path calling isMissingOperatorReadScopeError(err) with a GatewayRequestError instance
  • Impact: the exception occurs inside error-handling branches (e.g., in controllers like chat/logs/agents), potentially preventing the UI from rendering the intended error state and disrupting operator visibility/operations

Vulnerable code:

if (detailCode === ConnectErrorDetailCodes.AUTH_UNAUTHORIZED) {
  return true;
}

detailCode is never defined; the intended value likely comes from resolveGatewayErrorDetailCode(err).

Recommendation

Define detailCode using the existing resolver before comparing, and guard against null:

export function isMissingOperatorReadScopeError(err: unknown): boolean {
  if (!(err instanceof GatewayRequestError)) return false;

  const detailCode = resolveGatewayErrorDetailCode(err);
  if (detailCode === ConnectErrorDetailCodes.AUTH_UNAUTHORIZED) return true;

  return err.message.includes("missing scope: operator.read");
}

Add a unit test ensuring this helper never throws for arbitrary inputs (including GatewayRequestError with/without details).


2. 🟡 Control UI scope retention without device identity when dangerouslyDisableDeviceAuth enabled

Property Value
Severity Medium
CWE CWE-285
Location src/gateway/server/ws-connection/message-handler.ts:542-550

Description

dangerouslyDisableDeviceAuth (mapped to controlUiAuthPolicy.allowBypass) now prevents clearing self-declared scopes for Control UI connections that lack a bound device identity.

  • Input: client-provided connectParams.scopes (e.g., operator.read) in the WS connect request.
  • AuthZ guard removed: when device is missing, scopes used to be cleared for token/password/trusted-proxy connections even if other checks allowed the connection.
  • Impact: with gateway.controlUi.dangerouslyDisableDeviceAuth=true, a client can connect as Control UI without a device identity and retain requested operator scopes, enabling privileged RPCs such as chat.history (as asserted by the newly added test).

Vulnerable logic:

if (
  !device &&
  (decision.kind !== "allow" ||
    (!controlUiAuthPolicy.allowBypass &&
      !preserveInsecureLocalControlUiScopes &&
      (authMethod === "token" || authMethod === "password" || trustedProxyAuthOk)))
) {
  clearUnboundScopes();
}

This turns dangerouslyDisableDeviceAuth into a device-auth bypass + scope-claim bypass for operator Control UI sessions. If a shared gateway token/password is obtained (or auth is otherwise satisfied), an attacker can request elevated scopes without the usual pairing/device binding protections.

Recommendation

Do not allow self-declared scopes to persist on device-less connections, even when skipping device identity, unless there is a separate strong binding/authorization mechanism.

Options (pick one consistent with intended threat model):

  1. Always clear scopes when !device and then explicitly assign a minimal safe scope set for bypass mode:
if (!device) {
  clearUnboundScopes();
  if (controlUiAuthPolicy.allowBypass && isControlUi && role === "operator") {// e.g. allow only non-sensitive operations or a dedicated break-glass scope
    scopes = ["operator.read"]; // or [] and gate break-glass RPCs separately
    connectParams.scopes = scopes;
  }
}
  1. Restrict bypass to localhost/trusted proxy only:
if (controlUiAuthPolicy.allowBypass && !isLocalClient && !trustedProxyAuthOk) {// reject or clear scopes
}

Also consider requiring an explicit allowlist of origins/hosts and additional admin-only configuration before enabling any bypass that permits privileged scopes without device binding.


Analyzed PR: #53110 at commit 3aea4a8

Last updated on: 2026-03-23T20:31:42Z

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 23, 2026

Greptile Summary

This PR fixes a multi-faceted operator.read scope regression in the Control UI by patching the gateway's bypass-path scope-clearing logic, expanding implied operator scopes at storage time, ignoring under-scoped cached device tokens in the browser client, and replacing raw gateway errors with a clear degraded-state message across all main-page controllers.

Key changes:

  • message-handler.ts: adds !controlUiAuthPolicy.allowBypass to the clearUnboundScopes guard, so the dangerouslyDisableDeviceAuth bypass no longer silently strips requested scopes.
  • device-auth.ts: normalizeDeviceAuthScopes now expands operator.write → +operator.read and operator.admin → +operator.read +operator.write at store time, ensuring cached/granted tokens reflect full read capability.
  • gateway.ts: storedTokenCanRead check rejects under-scoped cached operator device tokens (backward-compatible with pre-fix stored tokens via write/admin fallbacks).
  • scope-errors.ts (new): shared isMissingOperatorReadScopeError / formatMissingOperatorReadScopeMessage utilities consumed by all major controller load functions.
  • All affected controllers (chat, agents, channels, cron, logs, presence, sessions, usage) now degrade gracefully with a targeted message instead of surfacing raw gateway errors.
  • Regression tests added for the bypass path, the under-scoped cached-token path, and the chat-page fallback.

One minor concern: isMissingOperatorReadScopeError uses AUTH_UNAUTHORIZED as its primary detection signal (see inline comment). Because AUTH_UNAUTHORIZED is the default branch in resolveAuthConnectErrorDetailCode, it can be emitted for unrelated auth failures, which could show a misleading "missing operator.read" message. The message-based fallback (err.message.includes("missing scope: operator.read")) is more precise. A clarifying code comment or a swap in check order would reduce future confusion when the protocol gains a dedicated code.

Confidence Score: 5/5

  • Safe to merge — all four related failure modes are addressed with matching test coverage and the logic changes are narrow and well-scoped.
  • The three core code fixes (bypass guard, scope normalization, stale-token rejection) are individually correct and well-tested via new integration and unit tests. The controller-level error handling is consistent across all affected pages. The one P2 comment about AUTH_UNAUTHORIZED being a coarse signal is a pre-existing protocol limitation that the code already acknowledges; it doesn't affect correctness in the current gateway implementation.
  • ui/src/ui/controllers/scope-errors.ts — the AUTH_UNAUTHORIZED catch-all should be revisited once the gateway exposes a dedicated scope-failure detail code.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/ui/controllers/scope-errors.ts
Line: 8-11

Comment:
**`AUTH_UNAUTHORIZED` is a catch-all that may produce misleading messages**

`AUTH_UNAUTHORIZED` is the `default` case in `resolveAuthConnectErrorDetailCode` — it maps any *unrecognized* auth-failure reason to this code (see `src/gateway/protocol/connect-error-details.ts:86`). Treating it as a definitive signal for "missing `operator.read` scope" in RPC error details means that a future auth error that happens to land on the `default` branch (e.g. a token that was revoked mid-session, or a new server-side error reason without a dedicated detail code) will show the user:

> "This connection is missing operator.read, so X cannot be loaded yet."

…even when the real problem has nothing to do with scopes. The more specific `err.message.includes("missing scope: operator.read")` check on line 14 is already a better guard for this.

Consider making the `AUTH_UNAUTHORIZED` branch secondary — check the message-based guard first and only fall back to `AUTH_UNAUTHORIZED` as a coarse hint, or add a brief comment clarifying that `AUTH_UNAUTHORIZED` is currently used *exclusively* for scope failures in the RPC path so reviewers know this assumption needs to be revisited when the protocol gains a dedicated code.

```suggestion
  // AUTH_UNAUTHORIZED is the current server signal for scope failures in RPC responses.
  // The message-based fallback below catches cases where no detail code is set.
  if (detailCode === ConnectErrorDetailCodes.AUTH_UNAUTHORIZED) {
    return true;
  }
```

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

Reviews (1): Last reviewed commit: "ignore under-scoped cached control ui to..." | Re-trigger Greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
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: c2d9a3a163

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

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

Comment on lines +569 to +573
const storedScopes = storedEntry?.scopes ?? [];
const storedTokenCanRead =
params.role !== CONTROL_UI_OPERATOR_ROLE ||
storedScopes.includes("operator.read") ||
storedScopes.includes("operator.write") ||
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate cached scope list before calling includes

selectConnectAuth now assumes storedEntry.scopes is an array and calls includes on it, but the device-auth loader only validates that token is a string and can return entries with malformed scopes from stale/corrupted localStorage. In that case this path throws at connect time (e.g., includes is not a function), so the Control UI cannot complete handshake or recover. Coerce scopes with an Array.isArray guard before running scope checks.

Useful? React with 👍 / 👎.

@BunsDev BunsDev merged commit 3e2b3bd into main Mar 23, 2026
9 of 10 checks passed
@BunsDev BunsDev deleted the ui/scope-patch-clean branch March 23, 2026 19:57
iclem pushed a commit to iclem/openclaw that referenced this pull request Mar 23, 2026
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 <[email protected]>
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 24, 2026
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 <[email protected]>
(cherry picked from commit 3e2b3bd)
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 <[email protected]>
(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 <[email protected]>
(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) <[email protected]>

* 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) <[email protected]>

---------

Co-authored-by: Peter Steinberger <[email protected]>
Co-authored-by: Gustavo Madeira Santana <[email protected]>
Co-authored-by: Val Alexander <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: Andrew Demczuk <[email protected]>
Co-authored-by: Jealous <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>
Co-authored-by: Karan Uppal <[email protected]>
Co-authored-by: 2233admin <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
hzq001 pushed a commit to hzq001/openclaw that referenced this pull request Mar 24, 2026
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 <[email protected]>
furaul pushed a commit to furaul/openclaw that referenced this pull request Mar 24, 2026
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 <[email protected]>
Arry8 pushed a commit to Arry8/openclaw that referenced this pull request Mar 25, 2026
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 <[email protected]>
netandreus pushed a commit to netandreus/openclaw that referenced this pull request Mar 25, 2026
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 <[email protected]>
npmisantosh pushed a commit to npmisantosh/openclaw that referenced this pull request Mar 25, 2026
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 <[email protected]>
0x666c6f added a commit to 0x666c6f/openclaw that referenced this pull request Mar 26, 2026
…claw#105)

* fix(web-search): mark DuckDuckGo experimental

* docs(tools): update DuckDuckGo Search for landed plugin code

- Mark as experimental (not just unofficial)
- Add region and safeSearch tool parameters (from DDG schema)
- Add plugin config example for region/safeSearch defaults
- Document auto-detection order (100 = last)
- Note SafeSearch defaults to moderate
- Verified against extensions/duckduckgo/src/

* fix(agents): deny local MEDIA paths for MCP results

* Usage: include reset and deleted session archives (openclaw#43215)

Merged via squash.

Prepared head SHA: 49ed6c2
Co-authored-by: rcrick <[email protected]>
Co-authored-by: frankekn <[email protected]>
Reviewed-by: @frankekn

* docs(tools): soften DDG wording (scrapes -> pulls/gathers)

* fix(build): add stable memory-cli dist entry (openclaw#51759)

Co-authored-by: oliviareid-svg <[email protected]>
Co-authored-by: Frank <[email protected]>

* refactor!: drop legacy CLAWDBOT env compatibility

* refactor!: remove moltbot state-dir migration fallback

* fix(gateway): preserve async hook ingress provenance

* fix(ci): write dist build stamp after builds

* perf: trim vitest hot imports and refresh manifests

* fix(security): unwrap time dispatch wrappers

* fix(plugin-sdk): fall back to src root alias files

* fix(ci): skip docs-only preflight pnpm audit

* docs(changelog): note time exec approval fix

* docs: refresh plugin-sdk api baseline

* fix(runtime): make dist-runtime staging idempotent

* fix(media): bound remote error-body snippet reads

* fix(gateway): gate internal command persistence mutations

* fix: restrict remote marketplace plugin sources

* fix(runtime): skip peer resolution for bundled plugin deps

* docs(agents): prefer current test model examples

* fix(exec): escape invisible approval filler chars

* test(models): refresh example model fixtures

* fix(security): unify dispatch wrapper approval hardening

* fix(security): harden explicit-proxy SSRF pinning

* fix: gate synology chat reply name matching

* docs: clarify sessions_spawn ACP vs subagent policies

* refactor(exec): split wrapper resolution modules

* refactor(exec): make dispatch wrapper semantics spec-driven

* refactor(exec): share wrapper trust planning

* refactor(exec): rename wrapper plans for trust semantics

* fix: include .npmrc in onboard docker build

* test: trim docker live auth mounts

* Docs: refresh config baseline for Synology Chat

* refactor: clarify synology delivery identity names

* refactor: centralize synology dangerous name matching

* refactor: narrow synology legacy name lookup

* refactor: audit synology dangerous name matching

* refactor: dedupe synology config schema

* fix: normalize scoped vitest filter paths

* fix(voice-call): harden webhook pre-auth guards

* fix(synology-chat): fail closed shared webhook paths

* docs: credit nexrin in synology changelog

* test: fix base vitest thread regressions

* test: finish base vitest thread fixture fixes

* test(voice-call): accept oversize webhook socket resets

* test: honor env auth in gateway live probes

* fix: harden plugin docker e2e

* Docs: align MiniMax examples with M2.7

* fix(ci): restore stale guardrails and baselines

* Test: isolate qr dashboard integration suite

* Gateway: resolve fallback plugin context lazily

* fix: bind bootstrap setup codes to node profile

* fix(tlon): unify settings reconciliation semantics

* refactor(synology-chat): type startup webhook path policy

* docs(synology-chat): clarify multi-account webhook paths

* refactor: unify minimax model and failover live policies

* docs: sync minimax m2.7 references

* fix: harden Windows Parallels smoke installs

* docs: reorder unreleased changelog by user impact

* refactor: remove embedded runner cwd mutation

* Infra: support shell carrier allow-always approvals

* refactor: centralize bootstrap profile handling

* refactor: reuse canonical setup bootstrap profile

* fix(plugin-sdk): resolve hashed diagnostic events chunks

* fix(plugin-sdk): normalize hashed diagnostic event exports

* test: fix ci env-sensitive assertions

* fix(gateway): fail closed on unresolved discovery endpoints

* feat: add slash plugin installs

* fix(media): block remote-host file URLs in loaders

* fix(media): harden secondary local path seams

* test: harden no-isolate reply teardown

* docs(changelog): add Windows media security fix

* refactor(gateway): centralize discovery target handling

* test: narrow live transcript scaffolding strip

* test: fix ci docs drift and bun qr exit handling

* fix(browser): enforce node browser proxy allowProfiles

* refactor(media): share local file access guards

* test: stabilize ci test harnesses

* test: harden no-isolate test module resets

* fix(plugins): preserve live hook registry during gateway runs

* test: fix channel summary registry setup

* test: harden isolated test mocks

* chore(plugins): remove opik investigation checkpoints

* ACPX: align pinned runtime version (openclaw#52730)

* ACPX: align pinned runtime version

* ACPX: drop version example from help text

* test: stop leaking image workspace temp dirs

* fix(android): gate canvas bridge to trusted pages (openclaw#52722)

* fix(android): gate canvas bridge to trusted pages

* fix(changelog): note android canvas bridge gating

* Update apps/android/app/src/main/java/ai/openclaw/app/node/CanvasActionTrust.kt

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

* fix(android): snapshot canvas URL on UI thread

---------

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

* test: isolate base vitest thread blockers

* fix: sync agent and autoreply e2e updates

* test: harden no-isolate mocked module resets

* docs: reorder unreleased changelog

* fix(changelog): note windows media path guardrails (openclaw#52738)

* fix: alphabetize web search provider listings

* docs: clarify unreleased breaking changes

* test: harden ci isolated mocks

* fix: align websocket stream fallback types

* test: finish no-isolate suite hardening

* style: format image-generation runtime tests

* fix(memory-core): register memory tools independently to prevent coupled failure (openclaw#52668)

Merged via admin squash because current required CI failures are inherited from base and match latest `main` failures outside this PR's `memory-core` surface.

Prepared head SHA: df7f968
Co-authored-by: artwalker <[email protected]>
Reviewed-by: @frankekn

* fix(status): recompute fallback context window (openclaw#51795)

* fix(status): recompute fallback context window

* fix(status): keep live context token caps on fallback

* fix(status): preserve fallback runtime context windows

* fix(status): preserve configured fallback context caps

* fix(status): keep provider-aware transcript context lookups

* fix(status): preserve explicit fallback context caps

* fix(status): clamp fallback configured context caps

* fix(status): keep raw runtime slash ids

* fix(status): refresh plugin-sdk api baseline

* fix(status): preserve fallback context lookup

* test(status): refresh plugin-sdk api baseline

* fix(status): keep runtime slash-id context lookup

---------

Co-authored-by: create <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Co-authored-by: RichardCao <[email protected]>

* fix(telegram): make buttons schema optional in message tool

The Telegram plugin injects a `buttons` property into the message tool
schema via `createMessageToolButtonsSchema()`, but without wrapping it
in `Type.Optional()`. This causes TypeBox to include `buttons` in the
JSON Schema `required` array.

In isolated sessions (e.g. cron jobs) where no `currentChannel` is set,
all plugin schemas are merged into the message tool. When the LLM calls
the message tool without a `buttons` parameter, AJV validation fails
with: `buttons: must have required property 'buttons'`.

Wrap the buttons schema in `Type.Optional()` so it is not required.

* fix: keep message-tool buttons optional for Telegram and Mattermost (openclaw#52589) (thanks @tylerliu612)

* test: update codex test fixtures to gpt-5.4

* fix: repair runtime seams after rebase

* fix: restore Telegram topic announce delivery (openclaw#51688) (thanks @mvanhorn)

When `replyLike.text` or `replyLike.caption` is an unexpected
non-string value (edge case from some Telegram API responses),
the reply body was coerced to "[object Object]" via string
concatenation. Add a `typeof === "string"` guard to gracefully
fall back to empty string, matching the existing pattern used
for `quoteText` in the same function.

Co-authored-by: Penchan <[email protected]>

* docs: sync generated release baselines

* test: isolate pi embedded model thread fixtures

* fix: restore provider runtime lazy boundary

* fix: preserve Telegram reply context text (openclaw#50500) (thanks @p3nchan)

* fix: guard Telegram reply context text (openclaw#50500) (thanks @p3nchan)

* fix: preserve Telegram reply caption fallback (openclaw#50500) (thanks @p3nchan)

---------

Co-authored-by: Ayaan Zaidi <[email protected]>

* fix: harden gateway SIGTERM shutdown (openclaw#51242) (thanks @juliabush)

* fix: increase shutdown timeout to avoid SIGTERM hang

* fix(telegram): abort polling fetch on shutdown to prevent SIGTERM hang

* fix(gateway): enforce hard exit on shutdown timeout for SIGTERM

* fix: tighten gateway shutdown watchdog

* fix: harden gateway SIGTERM shutdown (openclaw#51242) (thanks @juliabush)

---------

Co-authored-by: Ayaan Zaidi <[email protected]>

* build: prepare 2026.3.22-beta.1

* fix: restore provider runtime lazy boundary

* test: add parallels npm update smoke

* test: split pi embedded model thread fixtures

* fix: stop browser server tests from launching real chrome

* test: stabilize live provider docker probes

* fix: restart windows gateway after npm update

* test: isolate server-context browser harness imports

* test: inject model runtime hooks for thread-safe tests

* test: snapshot ci timeout investigation

* test: target gemini 3.1 flash alias

* test: stabilize trigger handling and hook e2e tests

* build: prepare 2026.3.22

* test: harden channel suite isolation

* test: inject thread-safe deps for agent tools

* test: raise timeout for slow provider auth normalization

* ci: stabilize windows and bun unit lanes

* test: inject thread-safe gateway and ACP seams

* test: isolate pi model and reset-model thread fixtures

* build: prepare 2026.3.23

* test: inject image-tool provider deps for raw threads

* test: stabilize e2e module isolation

* test: decouple vitest config checks from ambient env

* fix: harden parallels smoke agent invocation

* test: avoid repo-root perf profile artifacts

* test: inject thread-safe base seams

* fix: document Telegram asDocument alias (openclaw#52461) (thanks @bakhtiersizhaev)

* feat(telegram): add asDocument param to message tool

Adds `asDocument` as a user-facing alias for the existing `forceDocument`
parameter in the message tool. When set to `true`, media files (images,
videos, GIFs) are sent via `sendDocument` instead of `sendPhoto`/
`sendVideo`/`sendAnimation`, preserving the original file quality
without Telegram compression.

This is useful when agents need to deliver high-resolution images or
uncompressed files to users via Telegram.

`asDocument` is intentionally an alias rather than a replacement — the
existing `forceDocument` continues to work unchanged.

Changes:
- src/agents/tools/message-tool.ts: add asDocument to send schema
- src/agents/tools/telegram-actions.ts: OR asDocument into forceDocument
- src/infra/outbound/message-action-runner.ts: same OR logic for outbound path
- extensions/telegram/src/channel-actions.ts: read and forward asDocument
- src/channels/plugins/actions/actions.test.ts: add test case

* fix: restore channel-actions.ts to main version (rebase conflict fix)

* fix(test): match asDocument test payload to actual params structure

* fix(telegram): preserve forceDocument alias semantics

* fix: document Telegram asDocument alias (openclaw#52461) (thanks @bakhtiersizhaev)

---------

Co-authored-by: Бахтиер Сижаев <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>

* fix: refactor deepseek bundled plugin (openclaw#48762) (thanks @07akioni)

* fix: declare typebox runtime dep for mattermost plugin

* test: reset line webhook mocks between cases

* test: split attempt spawn-workspace thread fixtures

* test: remove replaced spawn-workspace monolith

* refactor: isolate attempt context engine thread helpers

* CI: remove npm release preview workflow (openclaw#52825)

* CI: remove npm release preview workflow

* Docs: align release maintainer skill with manual publish

* Docs: expand release maintainer skill flow

* test: stabilize gateway thread harness

* test: fix status plugin pagination expectation

* test: harden channel suite isolation

* build: sync lockfile for mattermost plugin

* fix: ensure env proxy dispatcher before MiniMax and OpenAI Codex OAuth flows (openclaw#52228)

Verified:
- pnpm install --frozen-lockfile
- NPM_CONFIG_CACHE=/tmp/openclaw-npm-cache-52228 pnpm build
- pnpm check
- pnpm test:macmini (failed on inherited pre-existing plugin contract test: src/plugins/contracts/registry.contract.test.ts missing deepseek in bundled provider contract registry outside this PR surface)

Co-authored-by: openperf <[email protected]>
Co-authored-by: Tak Hoffman <[email protected]>

* fix: restore ci gates

* test: stabilize channel ci gate

* docs: refresh generated config baseline

* release: verify control-ui assets are included in npm tarball

* release-check: include stderr/stdout when npm pack fails

* release: add changelog for control UI tarball check

* fix: keep session transcript pointers fresh after compaction (openclaw#50688)

Co-authored-by: Frank Yang <[email protected]>

* fix(msteams): isolate probe test env credentials

* release: automate macOS publishing (openclaw#52853)

* release: automate macOS publishing

* release: keep mac appcast in openclaw repo

* release: add preflight-only release workflow runs

* release: keep appcast updates manual

* release: generate signed appcast as workflow artifact

* release: require preflight before publish

* release: require mac app for every release

* docs: clarify every release ships mac app

* release: document Sparkle feed and SHA rules

* release: keep publish flow tag-based

* release: stabilize mac appcast flow

* release: document local mac fallback

* Update CHANGELOG.md

* Improve PR template regression prompts

* fix(agents): preserve anthropic thinking block order (openclaw#52961)

* fix(release): ship bundled plugins in pack artifacts

* fix(config): keep built-in channels out of plugin allowlists (openclaw#52964)

* fix(config): keep built-in channels out of plugin allowlists

* docs(changelog): note doctor whatsapp allowlist fix

* docs(changelog): move doctor whatsapp fix to top

* Update CHANGELOG.md

* fix(config): keep built-in auto-enable idempotent

* fix(release): preserve shipped channel surfaces in npm tar (openclaw#52913)

* fix(channels): ship official channel catalog (openclaw#52838)

* fix(release): keep shipped bundles in npm tar (openclaw#52838)

* build(release): fix rebased release-check helpers (openclaw#52838)

* fix(gateway): harden supervised lock and browser attach readiness

* fix(matrix): avoid duplicate runtime api exports

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

* docs(changelog): note release and matrix fixes

* fix(plugins): unblock Discord/Slack message tool sends and Feishu media (openclaw#52991)

* fix(plugins): unblock Discord and Slack message tool payloads

* docs(changelog): note Discord Slack and Feishu message fixes

* fix(channels): preserve external catalog overrides (openclaw#52988)

* fix(channels): preserve external catalog overrides

* fix(channels): clarify catalog precedence

* fix(channels): respect overridden install specs

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

* fix(voice-call): stabilize plivo v2 replay keys

* fix(gateway): require auth for canvas routes

* fix(clawhub): resolve auth token for skill browsing (openclaw#53017)

* fix(clawhub): resolve auth token for skill browsing

* docs(changelog): note clawhub skill auth fix

* fix(release): raise npm pack size budget

* Tests: fix fresh-main regressions (openclaw#53011)

* Tests: fix fresh-main regressions

* Tests: avoid chat notice cache priming

---------

Co-authored-by: Vincent Koc <[email protected]>

* fix(config): ignore stale plugin allow entries

* fix(browser): reuse running loopback browser after probe miss

* fix(clawhub): honor macOS auth config path (openclaw#53034)

* docs: fix nav ordering, missing pages, and stale model references

- Sort providers alphabetically in docs.json nav
- Sort channels alphabetically in docs.json nav (slack before synology-chat)
- Add install/migrating-matrix to Maintenance nav section (was orphaned)
- Remove zh-CN/plugins/architecture from nav (file does not exist)
- Add Voice Call to channels index page
- Add missing providers to providers index (DeepSeek, GitHub Copilot, OpenCode Go, Synthetic)
- Sort providers index alphabetically
- Update stale claude-3-5-sonnet model reference to claude-sonnet-4-6 in webhook docs

* fix(clawhub): preserve XDG auth path on macOS

* Agents: fix runtime web_search provider selection (openclaw#53020)

Co-authored-by: Vincent Koc <[email protected]>

* docs: fix CLI command tree, SDK import path, and tool group listing

- Remove non-existent 'secrets migrate' from CLI command tree
- Add actual secrets subcommands: audit, configure, apply
- Add missing plugin subcommands: inspect, uninstall, update, marketplace list
- Fix plugins info -> inspect (actual command name)
- Add message send and broadcast subcommands to command tree
- Remove misleading deprecated import from sdk-overview
- Add sessions_yield and subagents to group:sessions tool group docs
- Fix formatting

* fix(gateway): guard openrouter auto pricing recursion (openclaw#53055)

* test: refresh thread-safe agent fixtures

* Release: fix npm release preflight under pnpm (openclaw#52985)

Co-authored-by: Vincent Koc <[email protected]>

* docs(changelog): add channel catalog override note (openclaw#52988) (openclaw#53059)

* fix: harden update dev switch and refresh changelog

* fix(mistral): repair max-token defaults and doctor migration (openclaw#53054)

* fix(mistral): repair max-token defaults and doctor migration

* fix(mistral): add missing small-model repair cap

* fix(plugins): enable bundled Brave web search plugin by default (openclaw#52072)

Brave is a bundled web search plugin but was missing from
BUNDLED_ENABLED_BY_DEFAULT, causing it to be filtered out during
provider resolution. This made web_search unavailable even when
plugins.entries.brave.enabled was configured.

Fixes openclaw#51937

Co-authored-by: Ubuntu <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>

* fix(release): fail empty control ui tarballs

* Revert "fix(plugins): enable bundled Brave web search plugin by default (openclaw#52072)"

This reverts commit 0ea3c4d.

* Telegram: preserve inbound debounce order

* Telegram: fix fire-and-forget debounce order

* fix(reply): refresh followup drain callbacks

* Update CHANGELOG.md

* fix(reply): preserve no-debounce inbound concurrency

* fix(reply): clear idle followup callbacks

* fix(inbound): bound tracked debounce keys

* fix: preserve debounce and followup ordering (openclaw#52998) (thanks @osolmaz)

* fix(discord): reply on native command auth failures (openclaw#53072)

* docs(changelog): add missing recent fixes

* fix: bound tracked debounce key accounting

* fix packaged control ui asset lookup (openclaw#53081)

* fix(cli): preserve posix default git dir

* build: prepare 2026.3.23-beta.1

* test: harden canvas host undici isolation

* docs(changelog): credit web search runtime fix

* fix(openai-codex): bootstrap proxy on oauth refresh (openclaw#53078)

Verified:
- pnpm install --frozen-lockfile
- pnpm exec vitest run extensions/openai/openai-codex-provider.runtime.test.ts extensions/openai/openai-provider.test.ts

* release: harden preflight workflows (openclaw#53087)

* release: harden preflight-only workflows

* release: require main for publish runs

* release: select xcode for macos workflow

* release: retry flaky macos preflight steps

* ci: shard bun test lane

* 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 <[email protected]>

* build: prepare 2026.3.23

* fix(agents): prefer runtime snapshot for skill secrets

* docs(changelog): note skill secretref runtime fix

* fix(memory): bootstrap lancedb runtime on demand (openclaw#53111)

Bootstrap LanceDB into plugin runtime state on first use for packaged/global installs, keep @lancedb/lancedb plugin-local, and add regression coverage for bundled, cached, retry, and Nix fail-fast runtime paths.

Co-authored-by: Val Alexander <[email protected]>

* build: finalize 2026.3.23 release

* release: upload macos preflight artifacts (openclaw#53105)

* release: upload macos preflight artifacts

* release: speed up macos preflight

* release: use xlarge macos runner

* release: skip dmg path in macos preflight

* fix(subagents): recheck timed-out announce waits (openclaw#53127)

Recheck timed-out subagent announce waits against the latest runtime snapshot before announcing timeout, and keep that recheck best-effort so transient gateway failures do not suppress the announcement.

Co-authored-by: Val Alexander <[email protected]>

* docs(feishu): replace botName with name in config examples (openclaw#52753)

Merged via squash.

Prepared head SHA: 5237726
Co-authored-by: haroldfabla2-hue <[email protected]>
Co-authored-by: altaywtf <[email protected]>
Reviewed-by: @altaywtf

* fix(plugins): accept clawhub uninstall specs

* test(auth): align device scope expectations (openclaw#53151)

* fix: prevent delivery-mirror re-delivery and raise Slack chunk limit (openclaw#45489)

Merged via squash.

Prepared head SHA: c7664c7
Co-authored-by: theo674 <[email protected]>
Co-authored-by: altaywtf <[email protected]>
Reviewed-by: @altaywtf

* Infra: tighten shell-wrapper positional-argv allowlist matching (openclaw#53133)

* Infra: tighten shell carrier allowlist matching

* fix(security): tighten shell carrier allowlist matcher

* fix: generalize api_error detection for fallback model triggering (openclaw#49611)

Co-authored-by: Ayush Ojha <[email protected]>
Co-authored-by: altaywtf <[email protected]>

* feat(modelstudio): add standard (pay-as-you-go) DashScope endpoints for Qwen (openclaw#43878)

Add Standard API Key auth methods for China (dashscope.aliyuncs.com)
and Global/Intl (dashscope-intl.aliyuncs.com) pay-as-you-go endpoints
alongside the existing Coding Plan (subscription) endpoints.

Also updates group label to 'Qwen (Alibaba Cloud Model Studio)' and
fixes glm-4.7 -> glm-5 in Coding Plan note messages.

Co-authored-by: wenmeng zhou <[email protected]>

* Release: privatize macOS publish flow (openclaw#53166)

* fix(diagnostics): redact credentials from cache-trace diagnostic output

Refs openclaw#53103

* Release: document manual macOS asset upload (openclaw#53178)

* Release: document manual macOS asset upload

* Release: document macOS smoke-test mode

* docs(changelog): reorder release highlights

* test(whatsapp): stabilize login coverage in shared workers

* test(whatsapp): preserve session exports in login coverage

* test(whatsapp): preserve media test module exports

* test(whatsapp): preserve harness session exports

* fix(ci): stabilize whatsapp extension checks

* test: make update-cli checkout path assertion platform-safe

* fix(auth): prevent stale auth store reverts (openclaw#53211)

* Doctor: prune stale plugin allowlist and entry refs (openclaw#53187)

Signed-off-by: sallyom <[email protected]>

* test: stabilize test isolation

* test: update command coverage

* test: expand gemini live transcript stripping

* test: fix update-cli default path assertion

* chore(sre:PLA-920): adopt upstream sync changes

* fix(sre:PLA-920): align branch with adopted upstream tree

* build(sre:PLA-920): refresh dist artifacts

* test(sre:PLA-920): align incident-format expectations

---------

Signed-off-by: sallyom <[email protected]>
Co-authored-by: Vincent Koc <[email protected]>
Co-authored-by: Peter Steinberger <[email protected]>
Co-authored-by: Rick_Xu <[email protected]>
Co-authored-by: rcrick <[email protected]>
Co-authored-by: frankekn <[email protected]>
Co-authored-by: oliviareid-svg <[email protected]>
Co-authored-by: oliviareid-svg <[email protected]>
Co-authored-by: Frank <[email protected]>
Co-authored-by: scoootscooob <[email protected]>
Co-authored-by: ruochen <[email protected]>
Co-authored-by: Onur Solmaz <[email protected]>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Frank Yang <[email protected]>
Co-authored-by: artwalker <[email protected]>
Co-authored-by: RichardCao <[email protected]>
Co-authored-by: create <[email protected]>
Co-authored-by: RichardCao <[email protected]>
Co-authored-by: liuyang <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>
Co-authored-by: Matt Van Horn <[email protected]>
Co-authored-by: Penchan <[email protected]>
Co-authored-by: Penchan <[email protected]>
Co-authored-by: Julia Bush <[email protected]>
Co-authored-by: Bakhtier Sizhaev <[email protected]>
Co-authored-by: Бахтиер Сижаев <[email protected]>
Co-authored-by: wangchunyue <[email protected]>
Co-authored-by: Tak Hoffman <[email protected]>
Co-authored-by: evann <[email protected]>
Co-authored-by: Robin Waslander <[email protected]>
Co-authored-by: Sathvik Veerapaneni <[email protected]>
Co-authored-by: Nimrod Gutman <[email protected]>
Co-authored-by: Luke <[email protected]>
Co-authored-by: scoootscooob <[email protected]>
Co-authored-by: Jamil Zakirov <[email protected]>
Co-authored-by: TheRipper <[email protected]>
Co-authored-by: Quinn H. <[email protected]>
Co-authored-by: Ubuntu <[email protected]>
Co-authored-by: Val Alexander <[email protected]>
Co-authored-by: betoblair <[email protected]>
Co-authored-by: haroldfabla2-hue <[email protected]>
Co-authored-by: altaywtf <[email protected]>
Co-authored-by: Altay <[email protected]>
Co-authored-by: theo674 <[email protected]>
Co-authored-by: theo674 <[email protected]>
Co-authored-by: Ayush Ojha <[email protected]>
Co-authored-by: Ayush Ojha <[email protected]>
Co-authored-by: George Zhang <[email protected]>
Co-authored-by: wenmeng zhou <[email protected]>
Co-authored-by: Onur <[email protected]>
Co-authored-by: Sally O'Malley <[email protected]>
Interstellar-code pushed a commit to Interstellar-code/operator1 that referenced this pull request Apr 2, 2026
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 <[email protected]>
(cherry picked from commit 3e2b3bd)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant