Summary
During the most recent content-only upstream sync (OpenClaw 2026.4.22 → 2026.4.24), a
revert-fidelity audit compared every kept fork file against the true upstream 2026.4.24 blob and
found a set of upstream fixes that landed in the sync window but are missing from the fork's kept
code paths. RemoteClaw guts OpenClaw's execution engine but keeps the channel-adapter, gateway,
plugin, and config layers — so these fixes apply to code we still ship and should be re-ported.
None are blocking (the sync PR already merged; the fork's aggressive gutting removed most vulnerable
upstream paths wholesale). Two are medium (advertised/expected security controls that are absent
on live paths); the rest are low correctness/UX drift. Upstream commit hashes below refer to the
openclaw/openclaw history (fetched in this repo as the openclaw remote — git show <hash> works).
Priority 1 — Medium
1. sessions.compact is missing the webchat session-mutation guard
File: src/gateway/server-methods/sessions.ts
Upstream fix: commit 873dce178de — fix(gateway): block webchat session compaction mutations (PR openclaw#70716)
Upstream added a client-class guard so that webchat clients cannot mutate session state: it
extended rejectWebchatSessionMutation to cover the compact (and restore) actions and called it
at the top of the sessions.compact handler.
In the fork, sessions.patch and sessions.delete do call rejectWebchatSessionMutation
(with client + isWebchatConnect in their handler signatures), but sessions.compact does not:
// src/gateway/server-methods/sessions.ts (fork, current)
"sessions.compact": async ({ params, respond }) => { // <-- no `client`, no `isWebchatConnect`, no guard
...
const compactTarget = await updateSessionStore(storePath, (store) => { ... }); // mutates session transcript
rejectWebchatSessionMutation's action type is still the pre-fix "patch" | "delete".
Impact: a webchat-class client can invoke sessions.compact to compact/mutate session transcripts
even though the same client class is blocked from patch/delete. Bounded (why this is medium, not
high): sessions.compact requires ADMIN_SCOPE (see src/gateway/method-scopes.ts, the
[ADMIN_SCOPE] group), and the scope layer is still enforced (default-deny), so the exposure is an
admin-scoped webchat connection (e.g. an XSS'd control-UI session), and the impact is bounded to
compaction. It is a defense-in-depth gap, not an unauthenticated bypass.
Fix: give the sessions.compact handler client + isWebchatConnect, extend the
rejectWebchatSessionMutation action union to include "compact", and call the guard before mutating
(mirroring sessions.patch/sessions.delete).
Verify the gap:
git show HEAD:src/gateway/server-methods/sessions.ts | grep -n 'sessions.compact\|rejectWebchatSessionMutation'
# patch/delete call the guard; compact does not.
2. plugins.entries.*.hooks.allowConversationAccess is advertised but not enforced
File: src/plugins/registry.ts
Upstream fix: commit 51f9f94cc36 — fix(hooks): harden cli transcript loading (PR openclaw#70786)
Upstream added a permission gate: a non-bundled (third-party) plugin may only register
conversation/transcript hooks if it explicitly opts in with
plugins.entries.<id>.hooks.allowConversationAccess=true; otherwise the typed-hook registration is
blocked with a diagnostic. The gate uses an isConversationHookName(hookName) check.
In the fork, registerTypedHook enforces only allowPromptInjection — there is no
isConversationHookName gate anywhere (git grep isConversationHookName returns nothing). Yet the
allowConversationAccess config field is fully plumbed and advertised:
src/config/zod-schema.ts (allowConversationAccess: z.boolean().optional())
src/config/schema.base.generated.ts, src/config/schema.help.ts, src/config/schema.labels.ts
("Allow Conversation Access Hooks")
src/plugins/config-normalization-shared.ts
and conversation/transcript hooks exist (src/plugins/hooks.ts — "Allows plugins to analyze completed
conversations", transcript-append hooks), and non-bundled plugins are activatable via the allow-list
(src/plugins/config-activation-shared.ts).
Impact: an operator-allowed third-party plugin gets ungated access to completed conversation
transcripts, and the advertised allowConversationAccess opt-in control is inert — setting it to
false (or relying on the default) does nothing. This is a false-assurance confidentiality/permission
gap. Medium (requires an allow-listed non-bundled plugin, so not high).
Fix: port isConversationHookName and enforce it in registerTypedHook: for record.origin !== "bundled", block conversation-hook registration unless policy?.allowConversationAccess === true
(mirroring the existing allowPromptInjection enforcement).
Verify the gap:
git grep -n isConversationHookName # (empty — gate absent)
git show HEAD:src/plugins/registry.ts | grep -n 'allowPromptInjection\|allowConversationAccess'
Priority 2 — Low (correctness / UX / privacy drift; re-port opportunistically)
All are live but low-impact. Each row is file — upstream commit (subject) — what's missing:
Privacy (data-minimization):
src/auto-reply/reply/groups.ts — e0f5961e289 (fix: harden group chat prompt metadata) — upstream
removed the group name and participant list from the agent prompt; the fork's
buildGroupChatContext still emits You are in the ... group chat "${subject}" and
Participants: ${members} (both sent to the LLM). Low (the model already receives the group messages).
Channel adapters:
extensions/slack/src/monitor/message-handler.ts — 2b5c719a628 (process thread broadcasts as
messages) — skip-gate lacks subtype !== "thread_broadcast", so thread_broadcast messages are
silently skipped.
extensions/slack/src/send.ts — d399ac74f75 (hash token cache keys) — createSlackDmCacheKey
uses the raw token as a Map key (upstream hashes it); and 107d2b7a096 (preserve rapid send
ordering) — no send-queue serialization.
extensions/whatsapp/src/channel.ts + src/login-qr.ts — 245451b6a9c (keep QR login state in
sync) — QR wait flow not bounded to current QR metadata (stale-QR race).
extensions/whatsapp/src/auto-reply/deliver-reply.ts — 18c98316f7a (canonicalize outbound media
delivery) — pre-fix media path; no outbound-media-contract canonicalization.
extensions/feishu/src/send.ts — bb5e278f63b (stabilize topic group session keys) — chatType
omits topic_group.
extensions/slack/src/channel.ts — e40d7abda9f (preserve real thread anchors); slack
monitor/media.ts/prepare-content.ts/prepare.ts — d1cc54866d6 (file placeholders omit fileId);
monitor/slash.ts — 76a4c167f7e (group-DM labelled "channel").
Discord:
extensions/discord/src/draft-stream.ts — 70fd1c91aa8 — streaming preview sends omit
allowed_mentions: { parse: [] }, so a preview containing @everyone/@here triggers real pings.
extensions/discord/src/monitor/reply-delivery.ts — 3dba3d8b35c — the Discord reply path runs no
message_sending plugin hooks (they are wired for telegram/slack/deliver.ts).
Gateway (correctness/resource):
src/gateway/server-methods/agent.ts — 8cae2ed645e (agent RPC runs not registered in
chatAbortControllers, so chat.abort can't stop them) and 934dd5b3a73 (codex session-id routing
missing !requestedSessionId guard).
src/gateway/server-methods/chat.ts — 1ff07245f33 (chat.send omits addChatRun; a pre-dispatch
throw leaks the abort-controller entry).
src/gateway/call.ts — 77a1cbd5ffb + d39e34d31f3 (CLI gateway-client teardown uses sync
client.stop() without stopAndWait).
Infra / cron / control-UI:
src/infra/heartbeat-runner.ts — 9f6cda120d3 (clamp scheduler delay to Node setTimeout cap) — no
clamp, so a heartbeat.every > ~24.85d crash-loops the gateway (operator misconfig); and
349749f73de (exec-completion payloads not embedded in the heartbeat prompt).
src/cron/isolated-agent/session.ts — f0ceb4b68f5 (fresh-cron-session uses the old blocklist
instead of the allowlist isolation; the primary lastThreadId leak is already handled).
ui/src/ui/app-chat.ts + app-gateway.ts — 8513a14406f (no pendingAbort replay on reconnect);
ui/src/ui/chat/grouped-render.ts — 86f8c826e20 (avatar render skips bidi/invisible-control
sanitization; lit-escaped, so residual is bidi visual-spoof on identity-config only);
ui/src/ui/controllers/chat.ts — 1afbfdf451d + 3f63ba8fd80 (history-reload drops optimistic tail;
no heartbeat/empty-user hiding).
src/agents/subagent-orphan-recovery.ts + subagent-registry.ts — 845040214e1 (interrupted
subagent-run finalization/stale-sweep absent; note the fork's subagent waits are gateway-mediated with
a timeoutMs, which likely bounds the parent-hang differently).
CLI:
src/commands/agents.commands.delete.ts — 2cacd2097b5 (agents delete trashes the workspace
unconditionally; missing the shared-workspace-overlap retention guard — data loss, recoverable via
trash).
src/commands/onboard-helpers.ts — 8e40bdba902 (handleReset uses load-time CONFIG_PATH/
CONFIG_DIR instead of call-time resolveConfigPath()/resolveConfigDir()).
Summary
During the most recent content-only upstream sync (OpenClaw
2026.4.22→2026.4.24), arevert-fidelity audit compared every kept fork file against the true upstream
2026.4.24blob andfound a set of upstream fixes that landed in the sync window but are missing from the fork's kept
code paths. RemoteClaw guts OpenClaw's execution engine but keeps the channel-adapter, gateway,
plugin, and config layers — so these fixes apply to code we still ship and should be re-ported.
None are blocking (the sync PR already merged; the fork's aggressive gutting removed most vulnerable
upstream paths wholesale). Two are medium (advertised/expected security controls that are absent
on live paths); the rest are low correctness/UX drift. Upstream commit hashes below refer to the
openclaw/openclawhistory (fetched in this repo as theopenclawremote —git show <hash>works).Priority 1 — Medium
1.
sessions.compactis missing the webchat session-mutation guardFile:
src/gateway/server-methods/sessions.tsUpstream fix: commit
873dce178de— fix(gateway): block webchat session compaction mutations (PR openclaw#70716)Upstream added a client-class guard so that webchat clients cannot mutate session state: it
extended
rejectWebchatSessionMutationto cover thecompact(andrestore) actions and called itat the top of the
sessions.compacthandler.In the fork,
sessions.patchandsessions.deletedo callrejectWebchatSessionMutation(with
client+isWebchatConnectin their handler signatures), butsessions.compactdoes not:rejectWebchatSessionMutation's action type is still the pre-fix"patch" | "delete".Impact: a webchat-class client can invoke
sessions.compactto compact/mutate session transcriptseven though the same client class is blocked from
patch/delete. Bounded (why this is medium, nothigh):
sessions.compactrequiresADMIN_SCOPE(seesrc/gateway/method-scopes.ts, the[ADMIN_SCOPE]group), and the scope layer is still enforced (default-deny), so the exposure is anadmin-scoped webchat connection (e.g. an XSS'd control-UI session), and the impact is bounded to
compaction. It is a defense-in-depth gap, not an unauthenticated bypass.
Fix: give the
sessions.compacthandlerclient+isWebchatConnect, extend therejectWebchatSessionMutationaction union to include"compact", and call the guard before mutating(mirroring
sessions.patch/sessions.delete).Verify the gap:
2.
plugins.entries.*.hooks.allowConversationAccessis advertised but not enforcedFile:
src/plugins/registry.tsUpstream fix: commit
51f9f94cc36— fix(hooks): harden cli transcript loading (PR openclaw#70786)Upstream added a permission gate: a non-bundled (third-party) plugin may only register
conversation/transcript hooks if it explicitly opts in with
plugins.entries.<id>.hooks.allowConversationAccess=true; otherwise the typed-hook registration isblocked with a diagnostic. The gate uses an
isConversationHookName(hookName)check.In the fork,
registerTypedHookenforces onlyallowPromptInjection— there is noisConversationHookNamegate anywhere (git grep isConversationHookNamereturns nothing). Yet theallowConversationAccessconfig field is fully plumbed and advertised:src/config/zod-schema.ts(allowConversationAccess: z.boolean().optional())src/config/schema.base.generated.ts,src/config/schema.help.ts,src/config/schema.labels.ts("Allow Conversation Access Hooks")
src/plugins/config-normalization-shared.tsand conversation/transcript hooks exist (
src/plugins/hooks.ts— "Allows plugins to analyze completedconversations", transcript-append hooks), and non-bundled plugins are activatable via the allow-list
(
src/plugins/config-activation-shared.ts).Impact: an operator-allowed third-party plugin gets ungated access to completed conversation
transcripts, and the advertised
allowConversationAccessopt-in control is inert — setting it tofalse(or relying on the default) does nothing. This is a false-assurance confidentiality/permissiongap. Medium (requires an allow-listed non-bundled plugin, so not high).
Fix: port
isConversationHookNameand enforce it inregisterTypedHook: forrecord.origin !== "bundled", block conversation-hook registration unlesspolicy?.allowConversationAccess === true(mirroring the existing
allowPromptInjectionenforcement).Verify the gap:
Priority 2 — Low (correctness / UX / privacy drift; re-port opportunistically)
All are live but low-impact. Each row is
file — upstream commit (subject) — what's missing:Privacy (data-minimization):
src/auto-reply/reply/groups.ts—e0f5961e289(fix: harden group chat prompt metadata) — upstreamremoved the group name and participant list from the agent prompt; the fork's
buildGroupChatContextstill emitsYou are in the ... group chat "${subject}"andParticipants: ${members}(both sent to the LLM). Low (the model already receives the group messages).Channel adapters:
extensions/slack/src/monitor/message-handler.ts—2b5c719a628(process thread broadcasts asmessages) — skip-gate lacks
subtype !== "thread_broadcast", sothread_broadcastmessages aresilently skipped.
extensions/slack/src/send.ts—d399ac74f75(hash token cache keys) —createSlackDmCacheKeyuses the raw token as a Map key (upstream hashes it); and
107d2b7a096(preserve rapid sendordering) — no send-queue serialization.
extensions/whatsapp/src/channel.ts+src/login-qr.ts—245451b6a9c(keep QR login state insync) — QR wait flow not bounded to current QR metadata (stale-QR race).
extensions/whatsapp/src/auto-reply/deliver-reply.ts—18c98316f7a(canonicalize outbound mediadelivery) — pre-fix media path; no outbound-media-contract canonicalization.
extensions/feishu/src/send.ts—bb5e278f63b(stabilize topic group session keys) —chatTypeomits
topic_group.extensions/slack/src/channel.ts—e40d7abda9f(preserve real thread anchors); slackmonitor/media.ts/prepare-content.ts/prepare.ts—d1cc54866d6(file placeholders omit fileId);monitor/slash.ts—76a4c167f7e(group-DM labelled "channel").Discord:
extensions/discord/src/draft-stream.ts—70fd1c91aa8— streaming preview sends omitallowed_mentions: { parse: [] }, so a preview containing@everyone/@heretriggers real pings.extensions/discord/src/monitor/reply-delivery.ts—3dba3d8b35c— the Discord reply path runs nomessage_sendingplugin hooks (they are wired for telegram/slack/deliver.ts).Gateway (correctness/resource):
src/gateway/server-methods/agent.ts—8cae2ed645e(agent RPC runs not registered inchatAbortControllers, sochat.abortcan't stop them) and934dd5b3a73(codex session-id routingmissing
!requestedSessionIdguard).src/gateway/server-methods/chat.ts—1ff07245f33(chat.sendomitsaddChatRun; a pre-dispatchthrow leaks the abort-controller entry).
src/gateway/call.ts—77a1cbd5ffb+d39e34d31f3(CLI gateway-client teardown uses syncclient.stop()withoutstopAndWait).Infra / cron / control-UI:
src/infra/heartbeat-runner.ts—9f6cda120d3(clamp scheduler delay to Node setTimeout cap) — noclamp, so a
heartbeat.every> ~24.85d crash-loops the gateway (operator misconfig); and349749f73de(exec-completion payloads not embedded in the heartbeat prompt).src/cron/isolated-agent/session.ts—f0ceb4b68f5(fresh-cron-session uses the old blocklistinstead of the allowlist isolation; the primary
lastThreadIdleak is already handled).ui/src/ui/app-chat.ts+app-gateway.ts—8513a14406f(nopendingAbortreplay on reconnect);ui/src/ui/chat/grouped-render.ts—86f8c826e20(avatar render skips bidi/invisible-controlsanitization; lit-escaped, so residual is bidi visual-spoof on identity-config only);
ui/src/ui/controllers/chat.ts—1afbfdf451d+3f63ba8fd80(history-reload drops optimistic tail;no heartbeat/empty-user hiding).
src/agents/subagent-orphan-recovery.ts+subagent-registry.ts—845040214e1(interruptedsubagent-run finalization/stale-sweep absent; note the fork's subagent waits are gateway-mediated with
a
timeoutMs, which likely bounds the parent-hang differently).CLI:
src/commands/agents.commands.delete.ts—2cacd2097b5(agents deletetrashes the workspaceunconditionally; missing the shared-workspace-overlap retention guard — data loss, recoverable via
trash).
src/commands/onboard-helpers.ts—8e40bdba902(handleResetuses load-timeCONFIG_PATH/CONFIG_DIRinstead of call-timeresolveConfigPath()/resolveConfigDir()).