Skip to content

fix(msteams): enforce sender allowlist checks on SSO signin invokes [AI]#66033

Merged
pgondhi987 merged 3 commits intoopenclaw:mainfrom
pgondhi987:fix/fix-440
Apr 13, 2026
Merged

fix(msteams): enforce sender allowlist checks on SSO signin invokes [AI]#66033
pgondhi987 merged 3 commits intoopenclaw:mainfrom
pgondhi987:fix/fix-440

Conversation

@pgondhi987
Copy link
Copy Markdown
Contributor

Summary

  • Problem: The signin/tokenExchange and signin/verifyState invoke handler in extensions/msteams/src/monitor-handler.ts processed SSO token exchange without calling resolveMSTeamsSenderAccess(). Senders blocked by DM policy, channel allowlist, or group sender allowlist could complete token exchange and persist a delegated token in msteams-sso-tokens.json.
  • Why it matters: Every other invoke handler in the same file (feedback, adaptive card, file consent) and the main message handler already enforce all three access-control layers before processing. The SSO handler had zero layers — a policy bypass for any operator running dmPolicy: "allowlist" or groupPolicy: "allowlist".
  • What changed: Added isSigninInvokeAuthorized() — a new authorization guard that checks DM policy, channel/team gate, and group sender allowlist using resolveMSTeamsSenderAccess() before any token exchange work. The check fires after the mandatory 200 ACK (required by Teams to avoid "Something went wrong" UI errors) and before the sso config check.
  • What did NOT change: Token exchange logic, SSO config shape, token store schema, and all authorized sender paths are unchanged. The 200 ACK is still always sent.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: The SSO invoke handler (monitor-handler.ts — the signin/tokenExchange/signin/verifyState block) was written without a resolveMSTeamsSenderAccess() call, unlike every other invoke handler in the same file.
  • Missing detection / guardrail: No unit test exercised the SSO path with a sender whose DM/group/channel policy would deny them.
  • Contributing context: The SSO handler was introduced in 828ebd43d4; the feedback-invoke authorization fix (which introduced isFeedbackInvokeAuthorized) predates it but the pattern was not carried over.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/msteams/src/monitor-handler.sso.test.ts
  • Scenario the test should lock in: signin/tokenExchange and signin/verifyState invokes from DM-blocked, channel-blocked, and group-blocked senders are each dropped (no fetch calls, no token stored) while still receiving a 200 ACK.
  • Why this is the smallest reliable guardrail: Directly exercises the three resolveMSTeamsSenderAccess paths against the SSO handler with parameterized invite types, verifying both the drop and the absence of side-effects.
  • Existing test that already covers this: None (gap).
  • If no new test is added, why not: N/A — tests added in this PR.

User-visible / Behavior Changes

Unauthorized Teams senders (blocked by DM policy, channel allowlist, or group sender allowlist) will no longer have their signin/tokenExchange or signin/verifyState invokes processed. They continue to receive a 200 ACK (required by Teams protocol) but no token is exchanged or stored. Authorized senders are unaffected.

Diagram (if applicable)

Before:
[signin/tokenExchange invoke] -> [200 ACK] -> [handleSigninTokenExchangeInvoke] -> [tokenStore.save]

After:
[signin/tokenExchange invoke] -> [200 ACK] -> [isSigninInvokeAuthorized?]
                                                  No  -> [return, no token stored]
                                                  Yes -> [handleSigninTokenExchangeInvoke] -> [tokenStore.save]

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? Yes — unauthorized senders can no longer persist delegated tokens via the SSO path.
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: The change restricts who can trigger token exchange, matching the existing policy already enforced on all other invoke paths. No new surface is introduced.

Repro + Verification

Environment

  • OS: Linux (CI / Ubuntu)
  • Runtime/container: Node 22 / Bun
  • Model/provider: N/A (unit test only)
  • Integration/channel: MS Teams (msteams extension)
  • Relevant config: dmPolicy: "allowlist", groupPolicy: "allowlist", team/channel allowlist

Steps

  1. Run pnpm test extensions/msteams/src/monitor-handler.sso.test.ts
  2. Observe new parameterized test cases pass: does not process signin/tokenExchange for DM sender outside allowlist, ...channel outside route allowlist, ...group sender outside group allowlist (and matching signin/verifyState variants).

Expected

  • All 6 new blocked-sender test cases pass.
  • sendActivity called with { type: "invokeResponse", value: { status: 200 } } (ACK still sent).
  • fetchImpl called 0 times (no token exchange network call).
  • tokenStore.get(...) returns null (no token persisted).
  • deps.log.debug called with the expected drop message and name field.

Actual

  • All 6 new cases pass; no regressions in existing SSO test suite.

Evidence

  • Failing test/log before + passing after

New tests added in monitor-handler.sso.test.ts exercise all three blocked-sender scenarios × two invoke types. Tests confirm both the protocol-level 200 ACK and the absence of side-effects (no HTTP calls, no stored token).

Human Verification (required)

This PR was generated by OpenAI Codex and reviewed by an AI reviewer (Claude). No human has manually run or verified the code changes beyond automated test execution.

  • Verified scenarios: Unit test matrix covers DM-blocked, channel-blocked, and group-blocked senders for both signin/tokenExchange and signin/verifyState.
  • Edge cases checked: Authorized senders unaffected (existing tests); ACK always sent before auth check (Teams protocol requirement); sso not configured path still works (auth check fires first, but no SSO processing occurs regardless).
  • What you did not verify: Live Teams bot end-to-end with a real unauthorized sender; behavior under concurrent SSO invoke deduplication.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

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

Risks and Mitigations

  • Risk: The 200 ACK is sent before the authorization check. A future refactor that moves the ACK after the auth check would cause Teams UI errors for blocked senders.
    • Mitigation: Code comment at the ACK site explicitly documents this ordering requirement. Pattern is identical to the existing feedback invoke handler.

@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: M maintainer Maintainer-authored PR labels Apr 13, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 13, 2026

Greptile Summary

Fixes a security gap where signin/tokenExchange and signin/verifyState invoke handlers processed SSO token exchange without any sender access-control checks, allowing senders blocked by DM policy, channel allowlist, or group sender allowlist to persist a delegated token. The fix adds isSigninInvokeAuthorized() — a direct port of the existing isFeedbackInvokeAuthorized pattern — called after the mandatory 200 ACK and before any token exchange work. New parameterized tests cover all three blocked-sender variants × two invoke types, asserting both the protocol ACK and the absence of side-effects.

Confidence Score: 5/5

Safe to merge — the fix is a direct application of an established pattern with full test coverage and no logic regressions.

All findings are P2 style suggestions (duplication between the two invoke auth helpers). No logic bugs, no data-loss risk, no security regressions introduced. Existing and new tests pass. The implementation exactly mirrors the reviewed isFeedbackInvokeAuthorized pattern.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler.ts
Line: 104-150

Comment:
**Near-identical duplication of `isFeedbackInvokeAuthorized`**

`isSigninInvokeAuthorized` and `isFeedbackInvokeAuthorized` share identical structure — both call `resolveMSTeamsSenderAccess`, check DM policy, channel gate, and group sender in the same order, and return the same values. The only differences are the log message strings and the extra `name` field in the signin log contexts. A shared helper (e.g. `isInvokeAuthorized(context, deps, logPrefix)`) could reduce the surface that needs to stay in sync if access-check logic evolves.

Not a blocker — the pattern mirrors an established precedent — but worth considering before a third invoke type arrives.

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

Reviews (1): Last reviewed commit: "fix: address issue" | Re-trigger Greptile

Comment on lines +104 to +150
async function isSigninInvokeAuthorized(
context: MSTeamsTurnContext,
deps: MSTeamsMessageHandlerDeps,
): Promise<boolean> {
const resolved = await resolveMSTeamsSenderAccess({
cfg: deps.cfg,
activity: context.activity,
});
const { msteamsCfg, isDirectMessage, conversationId, senderId } = resolved;
if (!msteamsCfg) {
return true;
}

if (isDirectMessage && resolved.access.decision !== "allow") {
deps.log.debug?.("dropping signin invoke (dm sender not allowlisted)", {
sender: senderId,
conversationId,
name: context.activity.name,
});
return false;
}

if (
!isDirectMessage &&
resolved.channelGate.allowlistConfigured &&
!resolved.channelGate.allowed
) {
deps.log.debug?.("dropping signin invoke (not in team/channel allowlist)", {
conversationId,
teamKey: resolved.channelGate.teamKey ?? "none",
channelKey: resolved.channelGate.channelKey ?? "none",
name: context.activity.name,
});
return false;
}

if (!isDirectMessage && !resolved.senderGroupAccess.allowed) {
deps.log.debug?.("dropping signin invoke (group sender not allowlisted)", {
sender: senderId,
conversationId,
name: context.activity.name,
});
return false;
}

return true;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Near-identical duplication of isFeedbackInvokeAuthorized

isSigninInvokeAuthorized and isFeedbackInvokeAuthorized share identical structure — both call resolveMSTeamsSenderAccess, check DM policy, channel gate, and group sender in the same order, and return the same values. The only differences are the log message strings and the extra name field in the signin log contexts. A shared helper (e.g. isInvokeAuthorized(context, deps, logPrefix)) could reduce the surface that needs to stay in sync if access-check logic evolves.

Not a blocker — the pattern mirrors an established precedent — but worth considering before a third invoke type arrives.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler.ts
Line: 104-150

Comment:
**Near-identical duplication of `isFeedbackInvokeAuthorized`**

`isSigninInvokeAuthorized` and `isFeedbackInvokeAuthorized` share identical structure — both call `resolveMSTeamsSenderAccess`, check DM policy, channel gate, and group sender in the same order, and return the same values. The only differences are the log message strings and the extra `name` field in the signin log contexts. A shared helper (e.g. `isInvokeAuthorized(context, deps, logPrefix)`) could reduce the surface that needs to stay in sync if access-check logic evolves.

Not a blocker — the pattern mirrors an established precedent — but worth considering before a third invoke type arrives.

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 6d52a15ebff1f1b4011309fde757dc3e5a698601.

@pavan987
Copy link
Copy Markdown

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@pgondhi987 pgondhi987 merged commit 80b1fa1 into openclaw:main Apr 13, 2026
Kyzcreig added a commit to Kyzcreig/openclaw that referenced this pull request Apr 14, 2026
* fix(matrix): sync runtime dependency lockfile

* fix(matrix): mirror staged runtime dependencies

* fix(release): allow matrix runtime pack size

* [codex] Fix LM Studio header-auth follow-ups (openclaw#65806)

* fix: harden lmstudio header auth handling

* fix: suppress lmstudio shell env auth

* fix(docker): install bundled plugin deps after prune

* fix: extract shared session status runtime (openclaw#65807)

Merged via squash.

Prepared head SHA: f027bd6
Co-authored-by: dutifulbob <261991368+dutifulbob@users.noreply.github.com>
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
Reviewed-by: @osolmaz

* fix: preserve qmd command paths

* fix: harden qmd service startup

* test(release): align pack size budget assertion

* feat: Streamline Feishu channel onboarding with QR code scan-to-create flow (openclaw#65680)

Add QR-based app registration, improve Feishu onboarding flows, support direct login entry, add group chat policy setup, reduce log noise, and update docs.

* fix(ci): verify bundled plugin runtime deps

* fix(feishu): avoid sdk facade cycles

* fix(feishu): keep channel auth on local api barrel

* fix(feishu): break auth login barrel cycle

* fix(feishu): guard app registration fetches

* test: fix macos parallels gateway fallback

* ci: add stable npm dist-tag sync

* docs: clarify npm dist-tag auth

* feat(docs): add Hostinger installation guide and link in VPS document… (openclaw#65904)

* Run context-engine turn maintenance as idle-aware background work (openclaw#65233)

Merged via squash.

Prepared head SHA: e9f6c67
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix(logging) add failover log source and target (openclaw#65955)

* clarify failover log source and target

* fix embedded runner final assistant raw text helper

* feat(plugin-sdk): add claimable dedupe helper

* refactor(line): share replay dedupe guard

* refactor(feishu): reuse persistent dedupe lookups

* fix(qr): lazy load terminal runtime modules

* test(wizard): mock auth profile runtime seam

* fix(qr): lazy load terminal ascii renderer

* perf(wizard): keep explicit skip auth path cold

* refactor(feishu): share synthetic event dedupe claims

* perf(daemon): keep install auth env path cold

* fix(nextcloud-talk): release replay claims on handler failure

* fix(plugin-sdk): serialize claimable dedupe races

* fix(plugins): treat context-engine plugins as capabilities in status/inspect (openclaw#58766)

Merged via squash.

Prepared head SHA: 23269d2
Co-authored-by: zhuisDEV <95547369+zhuisDEV@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix(nextcloud-talk): make replay retries explicit

* fix(zalo): make replay retries explicit

* fix: validate resolved context engine contracts (openclaw#63222)

Merged via squash.

Prepared head SHA: 5f3a15c
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix(mattermost): make replay retries explicit

* fix(whatsapp): make inbound retries explicit

* perf(secrets): fast-path explicit channel target lookup

* perf(cli): skip redundant schema passes for structured dry runs

* fix(discord): make inbound retries explicit

* perf(agents): keep fallback auth store cold without sources

* fix(slack): make inbound retries explicit

* fix(matrix): make delivery replay retries explicit

* fix(line): make webhook replay retries explicit

* fix(feishu): make card action retries explicit

* fix(whatsapp): await write stream finish before returning encFilePath (openclaw#65896)

* fix(whatsapp): await write stream finish in encryptedStream to fix race-condition ENOENT crash

* fix(whatsapp): ship Baileys media hotfix on npm installs

* fix(whatsapp): keep Baileys hotfix postinstall best-effort

* fix(whatsapp): harden Baileys postinstall temp writes

* fix(whatsapp): preserve Baileys hotfix file mode

---------

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>

* perf(config): reuse validated best-effort snapshots

* perf(infra): cache login shell env probes

* fix(gateway): harden service entrypoint resolution (openclaw#65984)

Merged via squash.

Prepared head SHA: 31cbc33
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* fix(feishu): make bot menu retries explicit

* fix(feishu): keep comment replay closed after generic failures

* perf(cron): lazy-load isolated cli runner runtime

* fix(plugin-sdk): avoid leaking queue rejection cleanup

* fix(tasks): avoid leaking scheduled sweep failures

* perf(cron): keep auth profile runtime cold

* fix(telegram): swallow update watermark persistence failures

* perf(cron): narrow session runtime imports

* fix(auto-reply): release inbound dedupe after dispatch errors

* fix(auto-reply): avoid leaking inbound debounce cleanup

* perf(cron): isolate runtime-heavy seams

* fix(telegram): avoid leaking thread binding persist cleanup

* perf(cron): lazy-load embedded runtime branch

* fix: stop repeated unknown-tool loops (openclaw#65922)

Merged via squash.

Prepared head SHA: f352a27
Reviewed-by: @osolmaz

* fix(runtime): avoid leaking detached cleanup promises

* perf(cron): trim unused runtime barrel exports

* fix(tlon): release replay claims after handler failures

* fix(nostr): retry inbound events after handler failures

* perf(cron): narrow live switch error import

* perf(agents): keep model fallback auth runtime cold

* fix(telegram): block watermark advancement past failed updates

* perf(cron): lazy-load context and catalog lookups

* perf(cron): use session store read path

* perf(cron): lazy-load delivery subagent registry

* fix(voice-call): retry rejected inbound hangups

* perf(cron): lazy-load skills snapshot runtime

* fix(telegram): defer replay commit until update succeeds

* fix(voice-call): keep unknown-call replays retryable

* perf(outbound): use read-only channel registry seam

* perf(outbound): use loaded-only channel plugin reads

* fix(telegram): retry failed group migration updates

* perf(outbound): isolate id-like target resolution

* fix(telegram): retry failed reaction updates

* perf(agents): narrow failover helper imports

* perf(plugins): isolate manifest registry cache state

* perf(auth-profiles): narrow source check path imports

* fix(telegram): retry failed model callbacks

* fix(heartbeat): preserve Telegram topic routing for isolated heartbeats (openclaw#66035)

Merged via squash.

Prepared head SHA: 83b986a
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* fix(telegram): retry failed commands pagination callbacks

* perf(channels): isolate loaded target parsing

* perf(outbound): narrow loaded target channel reads

* perf(utils): isolate message channel normalization

* fix(telegram): retry failed plugin binding callbacks

* perf(cron): lazy-load delivery logger runtime

* fix(telegram): retry failed pagination preflight

* fix(telegram): retry failed model browser callbacks

* perf(channels): read bundled channel metadata directly

* perf(cron): use read-only allow-from store seam

* fix(mattermost): dedupe repeated model picker selects

* fix(browser): unblock managed loopback CDP startup and control (openclaw#66043)

Merged via squash.

Prepared head SHA: c3d0a99
Reviewed-by: @mbelinky

* perf(sessions): use loaded thread-info seam

* fix(voice-call): keep retryable errors replayable

* perf(cron): narrow execution and skill runtime imports

* fix(telegram): retry failed model selections

* perf(cron): use narrow bound-account lookup

* fix(telegram): retry failed approval callbacks

* test(cron): mock skills snapshot runtime seam

* fix(plugins): serialize interactive callback dedupe

* fix(nostr): dedupe deterministic rejected events

* perf(cron): drop stale skill snapshot runtime exports

* perf(cron): use lightweight model selection resolver

* perf(agents): use lightweight model fallback selection helpers

* perf(cron): use narrow verbose-level runtime seam

* Gateway/sessions: preserve shared session route on system events (openclaw#66073)

Merged via squash.

Prepared head SHA: 314a935
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* perf(channels): split hot-path message channel normalization

* perf(sessions): isolate reset policy helpers

* perf(cron): lazy-load run executor runtime

* perf(cron): lazy-load external content runtime

* perf(agents): isolate thinking default helper

* fix(browser): detect local attachOnly loopback CDP sessions (openclaw#66080)

Merged via squash.

Prepared head SHA: 90c1c10
Reviewed-by: @mbelinky

* perf(agents): isolate agent scope config helpers

* fix(session): clear stale thread route on system events

* perf(cron): lazy-load delivery runtime helpers

* perf(cron): keep skill filter runtime lazy

* fix(bluebubbles): lazy-refresh Private API status on send (openclaw#43764) (openclaw#65447)

* fix(bluebubbles): lazy refresh Private API cache on send to prevent silent reply threading degradation (openclaw#43764)

When the 10-minute server info cache expires, sends requesting reply
threading or effects silently degrade to plain messages. Add a lazy
async refresh of the cache in the send path when Private API features
are needed but status is unknown, preserving graceful degradation if
the refresh fails.

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

* fix(bluebubbles): apply lazy Private API refresh to attachment sends and add missing test coverage (openclaw#43764)

Attachment sends had the same cache-expiry bug as text sends: when the
10-minute Private API status cache TTL expired, reply threading metadata
was silently dropped. Apply the same lazy-refresh pattern from send.ts.

Also add the missing "refresh succeeds with private_api: false" test case
for both send.ts and attachments.ts — proves effects throw and reply
threading degrades without the "unknown" warning when the API is explicitly
disabled.

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

* chore: update no-raw-channel-fetch allowlist for test-harness line shift

Adding fetchBlueBubblesServerInfo to the probe mock module shifted
globalThis.fetch in test-harness.ts from line 128 to 130.

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

---------

Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(telegram): add inbound retry regressions (openclaw#66075)

Merged via squash.

Prepared head SHA: 175cd25
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* fix: expose telegram topic names in agent context (openclaw#65973) (thanks @ptahdunbar)

* feat(telegram): expose forum topic names in agent context

Telegram Bot API does not provide a method to look up forum topic names
by thread ID. This adds an in-memory LRU cache that learns topic names
from service messages (forum_topic_created, forum_topic_edited,
forum_topic_closed, forum_topic_reopened) and seeds from
reply_to_message.forum_topic_created as a fallback for pre-existing
topics.

The resolved topic name is surfaced as:
- TopicName in MsgContext (available to {{TopicName}} in templates)
- topic_name in the agent prompt metadata block
- topicName in plugin hook event metadata

Includes unit tests for the topic-name-cache module (11 tests including
eviction and read-recency).

Known limitation: cache is in-memory only; after a restart it falls back
to the creation-time name until a rename event is observed.

* refactor(telegram): distill topic name flow

* fix: expose telegram topic names in agent context (openclaw#65973) (thanks @ptahdunbar)

---------

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

* perf(config): skip shell env fallback for explicit empty vars

* fix(cron): stop unresolved next-run refire loops (openclaw#66083)

Merged via squash.

Prepared head SHA: b86ba58

* perf(daemon): lazy-load auth profile install helpers

* fix(config): redact sourceConfig and runtimeConfig alias fields in redactConfigSnapshot [AI] (openclaw#66030)

* fix: address issue

* docs: add changelog entry for PR merge

* perf(daemon): slim gateway install token imports

* fix(msteams): enforce sender allowlist checks on SSO signin invokes [AI] (openclaw#66033)

* fix: address issue

* fix: address PR review feedback

* docs: add changelog entry for PR merge

* perf(daemon): import install config helpers directly

* fix(browser): enforce SSRF policy on snapshot, screenshot, and tab routes [AI] (openclaw#66040)

* fix: address issue

* fix: address review feedback

* fix: finalize issue changes

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge

* perf(config): reuse prepared snapshots for daemon token writes

* perf(config): defer legacy web search registry reads

* Lobster: import published core runtime (openclaw#64755)

* Lobster: import published core runtime

* Changelog: add Lobster core runtime note

* Lobster: type embedded core runtime

* Lobster: keep package-boundary tsconfig narrow

* perf(config): use direct writes for gateway token persistence

* fix(heartbeat): force owner downgrade for untrusted hook:wake system events [AI-assisted] (openclaw#66031)

* fix: address issue

* fix: address PR review feedback

* fix: address review-pr skill feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* docs: add changelog entry for PR merge

* fix(cron): preserve unresolved next-run backoff (openclaw#66113)

Merged via squash.

Prepared head SHA: a553daa
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* agents: stop strict mode from hijacking chat turns

* perf(secrets): lazy-load provider env var exports

* fix: classify openrouter json 404 model errors

Rewrites the stale branch on top of current `main` and preserves the original issue as regression coverage for the exact OpenRouter JSON 404 payload from openclaw#51571.

No production behavior changes are introduced here; current `main` already classifies this payload as `model_not_found`, and this merge locks that in across the shared matcher, failover classifier, and fallback loop.

Co-authored-by: 屈定 <mrdear@users.noreply.github.com>
Co-authored-by: Altay <altay@uinaf.dev>

* test(cron): fix openclaw#66019 maintenance regression coverage (openclaw#66122)

Merged via squash.

Prepared head SHA: 7f2a604

* perf(config): use generated SecretRef policy metadata

* perf(config): skip cold runtime refresh on one-shot writes

* fix(trace command): Improve trace raw diagnostics and trace command UX (openclaw#66089)

* improve trace raw diagnostics and command acks

* address trace review feedback

* avoid sync transcript reads in raw trace

* preserve raw cli output for trace

* gate trace emission at reply time

* reflect raw trace mode in status surfaces

* fix(outbound): replay queued session context (openclaw#66025)

* fix(outbound): preserve replay session context

* fix(outbound): remove user work log

* changelog: note outbound session-context replay fix (openclaw#66025)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* fix(queue): split collect batches by auth context (openclaw#66024)

* fix(queue): split collect batches by auth context

Co-authored-by: zsx <git@zsxsoft.com>

* fix(queue): keep overflow summary on splits

* fix(queue): preserve grouped collect retry semantics

* fix(queue): drop USER.md from pr

* fix(queue): keep overflow summary in first auth group

* fix(queue): clear overflow summary state after first auth group

* fix(queue): narrow auth split key

* fix(queue): flush collect summary-only drains

* changelog: note collect-mode auth-context batch split (openclaw#66024)

---------

Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>

* perf(config): narrow channel legacy rule loading

* perf(config): scope dry-run legacy validation

* perf(agents): lazy-load cli runner seams

* perf(commands): narrow session test imports

* fix(memory-core): run Dreaming once per cron schedule (openclaw#66139)

Merged via squash.

Prepared head SHA: 48229a2
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* perf(commands): narrow agent config imports

* perf(commands): lazy-load agent secret resolution

* [codex] fix(ui): guard dreaming wiki plugin calls (openclaw#66140)

Merged via squash.

Prepared head SHA: 030562b
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* perf(agents): keep attempt execution runtime cold

* perf(agents): lazy-load delivery runtime

* perf(agents): lazy-load session store updates

* perf(agents): narrow session helper imports

* fix: allow plugin commands on Slack when channel supports native commands (openclaw#64578)

Merged via squash.

Prepared head SHA: 2ec97bf
Co-authored-by: rafaelreis-r <57492577+rafaelreis-r@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* perf(config): keep runtime compat migrations lightweight

* fix(ui): preserve user-selected session on reconnect and tab switch (openclaw#59611) thanks @loong0306

Fixes openclaw#57072 — chat UI state desync after route navigation.

- applySessionDefaults() now detects user-selected sessions and preserves them on reconnect
- Chat tab session switching consolidated to use switchChatSession() helper
- Overview session-key handler uses shared resetChatStateForSessionSwitch to prevent stale state leaks
- Session select dropdowns now set ?selected to reflect actual state

Co-authored-by: loong0306 <loong0306@gmail.com>
Co-authored-by: Nova <nova@openknot.ai>

* fix: count unknown-tool retries only when streamed (openclaw#66145)

Merged via squash.

Prepared head SHA: b79209c
Co-authored-by: Bob <dutifulbob@gmail.com>
Reviewed-by: @osolmaz

* fix(active-memory): Move active memory recall into the hidden prompt prefix (openclaw#66144)

* move active memory into prompt prefix

* document active memory prompt prefix

* strip active memory prefixes from recall history

* harden active memory prompt prefix handling

* hide active memory prefix in leading history views

* strip hidden memory blocks after prompt merges

* preserve user turns in memory recall cleanup

* fix(ui): replace marked.js with markdown-it to fix ReDoS UI freeze (openclaw#46707) thanks @zhangfnf

Replace marked.js with markdown-it for the control UI chat markdown renderer
to eliminate a ReDoS vulnerability that could freeze the browser tab.

- Configure markdown-it with custom renderers matching marked.js output
- Add GFM www-autolink with trailing punctuation stripping per spec
- Escape raw HTML via html_block/html_inline overrides
- Flatten remote images to alt text, preserve base64 data URI images
- Add task list support via markdown-it-task-lists plugin
- Trim trailing CJK characters from auto-linked URLs (RFC 3986)
- Keep marked dependency for agents-panels-status-files.ts usage

Co-authored-by: zhangfan49 <zhangfan49@baidu.com>
Co-authored-by: Nova <nova@openknot.ai>

* fix(ci): unblock discord boundary typing

* fix(ci): repair extension boundary contracts

* fix(ci): mirror whatsapp runtime dependency

* fix(ci): align cron and session tests with runtime

* fix(ci): clear residual tsgo blockers

* fix(ci): repair baileys lockfile snapshot

* fix(memory): unify default root memory handling (openclaw#66141)

* fix(memory): unify default root memory handling

* test(memory): align legacy migration expectation

* docs(changelog): tag qmd root-memory fix

* docs(changelog): append qmd root-memory entry

* docs(changelog): dedupe qmd root-memory entry

* docs(changelog): attribute qmd root-memory fix

---------

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

* fix: normalize OpenAI minimal reasoning

* chore: fix pulled lint assertion

* plugins: trim staged runtime cargo

* fix(ci): restore plugin-local whatsapp deps

* fix(ci): align cron tests with default model

* fix(ci): repair agent test mocks

* fix(ci): repair telegram topic cache typing

* fix(ci): repair telegram ui and watch regressions

* fix(stream): tighten voice stream ingress guards (openclaw#66027)

* fix(stream): tighten voice stream ingress guards

* fix(stream): address review follow-ups

* fix(stream): normalize trusted proxy ip matching

* changelog: note voice-call media-stream ingress guard tightening (openclaw#66027)

* fix(stream): require non-empty trusted proxy list before honoring forwarding headers

Without an explicit trusted proxy list, the prior gate treated every
remote as 'from a trusted proxy', so enabling trustForwardingHeaders
let any direct caller spoof X-Forwarded-For / X-Real-IP and rotate the
resolved IP per request to evade maxPendingConnectionsPerIp. Require
trustedProxyIPs to be non-empty AND match the remote before trusting
forwarding headers.

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* Feishu: tighten allowlist target canonicalization (openclaw#66021)

* fix(feishu): tighten allowlist id matching

* fix(feishu): address review follow-ups

* changelog: note Feishu allowlist canonicalization tightening (openclaw#66021)

* fix(feishu): collapse typed wildcard allowlist aliases to bare wildcard

Previously normalizeFeishuTarget folded chat:* / user:* / open_id:* /
dm:* / group:* / channel:* down to '*', so those entries acted as
allow-all. The new typed canonicalization was producing literal keys
(chat:*, user:*, ...) that never matched any sender, silently
flipping those configs from allow-all to deny-all. Restore the prior
behavior by collapsing a wildcard value to '*' inside
canonicalizeFeishuAllowlistKey.

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* fix(ci): mirror whatsapp runtime dependency

* docs(changelog): note perf fixes

* test: align failover source model expectation

* fix: sendPolicy deny should suppress delivery, not inbound processing (openclaw#53328) (openclaw#65461)

* fix: sendPolicy deny suppresses delivery, not inbound processing (openclaw#53328)

Previously, sendPolicy "deny" returned early before the agent dispatch,
preventing the agent from ever seeing the message. This broke the use
case of an agent listening on WhatsApp groups with sendPolicy: deny to
read messages without replying — the agent couldn't read them at all.

Move the deny gate from before the agent dispatch to after it. The agent
now processes inbound messages normally (context, memory, tool calls),
but all outbound delivery paths are suppressed: final replies, tool
results, block replies, working status, plan updates, typing indicators,
and TTS payloads.

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

* fix: propagate sendPolicy to ACP tail dispatch instead of hardcoded allow

The ACP tail dispatch path (ctx.AcpDispatchTailAfterReset) was passing
sendPolicy: "allow" unconditionally, which would bypass delivery
suppression in a /reset <tail> turn when the session has sendPolicy deny.

Pass through the resolved sendPolicy so the tail dispatch respects it.

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

* fix: guard before_dispatch hook and ACP tail dispatch under sendPolicy deny

before_dispatch handled replies were leaking through sendFinalPayload
before the suppressDelivery guard was checked. ACP tail dispatch (from
/new <tail>) was being rejected by acp-runtime.ts deny checks instead
of proceeding with delivery suppression handled downstream.

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

* auto-reply: propagate deny suppression to reply_dispatch

* fix(acp): suppress onReplyStart when user delivery is denied

When sendPolicy resolves to "deny", ACP tail dispatch still invoked
onReplyStart via startReplyLifecycle before the suppressUserDelivery
check. Channels wire onReplyStart to typing indicators, so deny-scoped
sessions could still emit outbound typing events on /reset <tail>
flows and command bypass paths.

Gate startReplyLifecycleOnce on suppressUserDelivery so the lifecycle
is marked started but the callback is skipped. Payload delivery was
already suppressed; this closes the typing-indicator leak flagged by
Codex review (PR openclaw#65461 P1/P2).

* fix(acp): route non-tail deny turns through ACP when suppression is wired

tryDispatchAcpReplyHook was returning early for non-tail, non-command ACP
turns under sendPolicy: "deny", causing ACP-bound sessions to fall back
to the embedded reply path instead of flowing through acpManager.runTurn.
That diverged ACP session state, tool calls, and memory whenever
delivery suppression was active.

Now the early-return only fires when sendPolicy is "deny" AND the event
lacks suppressUserDelivery — i.e., when downstream delivery suppression
is not wired up. When suppressUserDelivery is set, dispatch-acp-delivery
already drops outbound sends (see onReplyStart / deliver guards), so ACP
can safely run the turn with state consistency preserved.

Existing behavior preserved:
- Command bypass still overrides deny
- Tail dispatch still overrides deny
- Plain-text deny turns without suppression still short-circuit

Addresses Codex bot P1 feedback on openclaw#65461.

* fix: gate empty-body typing indicator behind suppressTyping (openclaw#53328)

* fix: guard plugin-binding + fast-abort outbound paths under sendPolicy deny

The original PR computed suppressDelivery inside the try block, which was
after two outbound paths:

1. The plugin-owned binding block (sendBindingNotice calls for
   unavailable/declined/error outcomes, plus the plugin's own "handled"
   outcome) ran before the suppressDelivery flag existed, so plugin
   notices still leaked under deny.
2. The fast-abort path dispatched "Agent was aborted." via
   routeReplyToOriginating / sendFinalReply before the flag existed.

Move resolveSendPolicy() above the plugin-binding block so suppressDelivery
covers every outbound path downstream, matching the PR description's claim
that "all outbound paths are guarded by the flag."

Plugin-bound inbound handling under deny: plugin handlers can emit
outbound replies we cannot rewind, so skip the claim hook entirely under
deny and fall through to normal (suppressed) agent processing.
touchConversationBindingRecord still runs so binding activity stays
tracked.

Fast-abort under deny: still run the abort and record the completed
state, just don't emit the abort reply.

Tests:
- suppresses the fast-abort reply under sendPolicy deny
- delivers the fast-abort reply normally when sendPolicy is allow
  (regression guard)
- skips plugin-bound claim hook under deny and falls through to
  suppressed agent dispatch

Addresses Codex review findings on PR openclaw#65461.

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

---------

Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(tts): allow OpenClaw temp directory paths in reply media normalizer (openclaw#63511)

Merged via squash.

Prepared head SHA: 0e9a6da
Co-authored-by: jetd1 <15795935+jetd1@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Reviewed-by: @grp06

* docs(changelog): note sendPolicy suppressDelivery + BB Private API cache fixes (openclaw#66220)

Two recently-merged fixes that shipped without CHANGELOG entries:

- PR openclaw#65461 (sendPolicy deny suppresses delivery, not inbound processing,
  closes openclaw#53328) — squash 0362f21
- PR openclaw#65447 (BB lazy-refresh Private API on send to prevent reply
  threading degradation, closes openclaw#43764) — squash 85cfba6

Backfilling under `## Unreleased` > `### Fixes` before the next release cut.

Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(changelog): add 2026.4.12 dedupe note

* fix: recover reasoning-only OpenAI turns (openclaw#66167)

* openclaw-11f.1: retry reasoning-only OpenAI turns

Regeneration-Prompt: |
  Patch the embedded runner so a signed reasoning-only assistant turn with no user-visible text is treated as recoverable instead of silently ending the run. Keep the change focused on the active OpenAI GPT-style path, retry the turn with an explicit visible-answer continuation instruction, and fall back to the existing incomplete-turn error handling only after retries are exhausted. Add regression coverage for the helper classification and for the outer run loop retry behavior, and keep unrelated provider behavior unchanged.

* openclaw-11f.1: address reasoning-only review feedback

Regeneration-Prompt: |
  Follow up on PR review feedback for the reasoning-only retry patch. Keep the fix narrow: move the retry limit into a named constant alongside the other retry-policy values, document why the limit is 2, and prevent reasoning-only auto-retries after any side effects so the runner falls back to the existing caution path instead of risking duplicate actions. Add regression coverage for the side-effect guard and the named limit behavior.

* openclaw-11f.1: drop local pebbles artifacts

Regeneration-Prompt: |
  Remove accidentally committed local pebbles tracker artifacts from the PR branch without changing runtime code. Keep the cleanup limited to deleting the tracked .pebbles files from version control, and rely on local git excludes for future pebbles activity so these files stay out of diffs.

* openclaw-11f.1: tighten reasoning-only retry guards

Regeneration-Prompt: |
  Follow up on the remaining review feedback for the reasoning-only retry path. Keep the fix narrow: do not auto-retry a reasoning-only turn when the assistant already terminated with stopReason error, and evaluate the OpenAI-specific retry guard against the provider/model metadata of the assistant turn that actually produced the partial output rather than the outer run configuration. Add regression coverage for both behaviors in the incomplete-turn runner tests.

* openclaw-11f.1: retry empty GPT turns once

Regeneration-Prompt: |
  Extend the embedded runner's GPT-style incomplete-turn recovery with a separate generic empty-response retry path. Keep it narrower than the existing reasoning-only recovery: one retry only, replay-safe only, no side effects, no assistant error turns, and scoped to the active assistant provider/model metadata. Add explicit warning logs when the empty-response retry triggers and when its single retry budget is exhausted, and add regression coverage for the success and exhaustion cases without changing broader provider fallback behavior.

* openclaw-11f.1: harden reasoning-only retry completion checks

Regeneration-Prompt: |
  Follow up on the remaining review feedback for the GPT-style recovery path. Keep the change narrow: only retry reasoning-only turns when there is no visible assistant answer yet, and if the reasoning-only retry budget is exhausted without any visible answer, surface the existing incomplete-turn error instead of treating reasoning-only payloads as a successful completion. Add focused regression coverage for both scenarios and preserve the adjacent empty-response retry behavior.

* openclaw-11f.1: preserve profile cooldown on retry exhaustion

Regeneration-Prompt: |
  Follow up on the final review comment for the GPT-style recovery path. Keep the change narrow: when the reasoning-only retry budget is exhausted and the run returns the incomplete-turn error early, preserve the same auth-profile cooldown behavior that the normal incomplete-turn branch already applies so multi-profile failover continues to work consistently. Verify the touched runner suites still pass.

* fix: recover GPT-style empty turns

Regeneration-Prompt: |
  Add the required changelog entry for the PR that hardens embedded GPT-style recovery of reasoning-only and empty-response turns. Keep the changelog update under ## Unreleased > ### Fixes, append-only, and include the PR number plus author attribution on the same line.

* test: launch macos parallels gateway in guest

* docs(changelog): tidy unreleased entries

* fix(outbound): suppress relay status placeholder leaks

* fix(ci): avoid frozen hook test clock hangs

* fix(slack): isolate doctor contract API (openclaw#63192)

* Slack: isolate doctor contract API

* chore: changelog

* fix(slack): move doctor changelog entry to Unreleased

* Plugins: lock Slack doctor sidecar metadata

* Slack: fix changelog entry placement

---------

Co-authored-by: @zimeg <zim@o526.net>
Co-authored-by: George Pickett <gpickett00@gmail.com>

* test(qa-lab): cover GPT-style broken turns

* test: extend macos parallels gateway timeout

* build: refresh a2ui bundle hash

* fix(hooks): pass workspaceDir in gateway session reset internal hook context (openclaw#64735)

* fix(hooks): pass workspaceDir in gateway session reset internal hook context

The gateway path (performGatewaySessionReset) omitted workspaceDir when
creating the internal hook event, while the plugin hook path
(emitGatewayBeforeResetPluginHook) in the same file correctly resolved and
passed it.  This caused the session-memory handler to fall back to
resolveAgentWorkspaceDir from the session key, which for default-agent
keys resolves to the shared default workspace instead of the per-agent
workspace.  Daily notes and memory files were written to the wrong
workspace in multi-agent setups.

Closes openclaw#64528

* docs(changelog): add session-memory workspace reset note

* fix(changelog): remove conflict markers

---------

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

* docs(gateway): Document Docker-out-of-Docker Paradox and constraint (openclaw#65473)

* docs: Detail Docker-out-of-Docker paradox and host path requirements

* docs: fix spelling inside sandboxing.md

* fix: grammar typo as suggested by Greptile

* Agents: fix Windows drive path join for read/sandbox tools (openclaw#54039) (openclaw#66193)

* Agents: fix Windows drive path join for read/sandbox tools (openclaw#54039)

* fix(agents): harden Windows file URL path mapping

* fix(agents): reject encoded file URL separators

* Update CHANGELOG.md

---------

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

* test: bound canvas auth helper waits

* chore(release): prepare 2026.4.14 beta

* build: prune runtime dependency type declarations

* fix: include apiKey in codex provider catalog to unblock models.json loading (openclaw#66180)

Merged via squash.

Prepared head SHA: ce61934
Co-authored-by: hoyyeva <63033505+hoyyeva@users.noreply.github.com>
Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com>
Reviewed-by: @BruceMacD

* fix(slack): align interaction auth with allowlists (openclaw#66028)

* fix(slack): align interaction auth with allowlists

* fix(slack): address review followups

* fix(slack): preserve explicit owners with wildcard

* chore: append Claude comments resolution worklog

* fix(slack): harden interaction auth with default-deny, mandatory actor binding, and channel type validation

- Add interactiveEvent flag to authorizeSlackSystemEventSender for stricter
  interactive control authorization
- Default-deny when no allowFrom or channel users are configured for
  interactive events (block actions, modals)
- Require expectedSenderId for all interactive event types; block actions
  pass Slack-verified userId, modals pass metadata-embedded userId
- Reject ambiguous channel types for interactive events to prevent DM
  authorization bypass via channel-type fallback
- Add comprehensive test coverage for all new behaviors

* fix(slack): scope interactive owner/allowFrom enforcement to interactive paths only

* fix(slack): preserve no-channel interactive default

* Update context-engine-maintenance test

* chore: remove USER.md worklog artifact

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

* changelog: note Slack interactive auth allowlist alignment (openclaw#66028)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>

* fix(media): fail closed on attachment canonicalization (openclaw#66022)

* fix(media): fail closed on attachment canonicalization

* fix(media): clarify attachment skip failures

* fix(media): preserve attachment URL fallback

* fix(media): preserve getPath URL fallback on blocked local paths

* changelog: note media attachment canonicalization fail-closed (openclaw#66022)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* Guard dangerous gateway config mutations (openclaw#62006)

* fix(gateway): guard dangerous config alias

* fix(gateway): ignore reordered dangerous flags

* fix(gateway): use id-based mapping identity and honor legacy alias baseline

* fix(gateway): tighten dangerous config matching

* fix(gateway): strip IPv6 brackets in isRemoteGatewayTarget hostname check

* fix(gateway): detect tunneled remote targets

* fix(gateway): match id-less hook mappings by fingerprint, not index

* fix(gateway): detect env-selected remote targets

* fix(gateway): resolve remote-target guard from live config, not captured opts

* fix(gateway): resolve remote-target guard from live config, not captured opts

* fix(gateway): treat loopback OPENCLAW_GATEWAY_URL as local when mode is not remote

* fix(gateway): preserve legacy dangerous hook edits

* fix(gateway): block dangerous plugin reactivation

* fix(gateway): handle dotted plugin IDs in dangerous-flag checks

* fix(gateway): honor plugin policy activation

* fix(gateway): block remote plugin activation changes via allow/deny/enabled

* fix(gateway): broaden loopback url detection

* fix(gateway): resolve plugin IDs by longest-prefix match

* fix(gateway): block remote slot activation

* fix(gateway): preserve legacy mapping identity during id+field transitions

* fix(gateway): block remote load-path and channel activation changes

* test(gateway): fix remote config mock typing

* fix(gateway): guard auto-enabled dangerous plugins

* fix(gateway): address P1 review comments on remote gateway mutation guards

- Treat all OPENCLAW_GATEWAY_URL targets as remote for mutation guards to prevent SSH tunnel bypasses
- Always load config fresh in isRemoteGatewayTargetForAgentTools to detect session changes
- Expand remote activation guard to cover auto-enable paths (auth.profiles, models.providers, agents.defaults, agents.list, tools.web.fetch.provider)
- Respect plugins.deny in manifest-missing fallback to prevent false negatives
- Fix hook mapping identity matching to properly handle id-less mappings by fingerprint
- Update tests to reflect new secure behavior for env-sourced gateway URLs

* fix(gateway): prevent hook mapping swap attacks via fingerprint-only matching

When both current and next tokens have fingerprints, match ONLY by fingerprint.
This prevents replacing one dangerous hook mapping with a different one at the
same array index from being incorrectly treated as 'already present'.

The previous fallback to index-based matching allowed bypasses where an attacker
could swap dangerous mappings at the same index without triggering the guard.

* fix(gateway): honor allowlist in fallback guard

* fix(gateway): treat empty plugin allowlist as unrestricted in manifest-missing fallback

* docs: update USER.md worklog for empty-allowlist fix

* fix(gateway): resolve review comments — type safety, auto-enable resilience, remote hardening edits

* docs: update USER.md worklog for review comment resolution

* fix(gateway): block remaining remote setup auto-enable paths

* fix(gateway): simplify dangerous config mutation guard to set-diff approach

Replace 400+ lines of hook fingerprinting, remote gateway detection,
plugin activation tracking, and auto-enable enumeration with a simple
set-diff against collectEnabledInsecureOrDangerousFlags — the same
enumeration openclaw security audit already uses.

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

* chore: remove USER.md audit log from PR

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

* changelog: note gateway-tool dangerous config mutation guard (openclaw#62006)

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(telegram): persist topic-name cache

* test(telegram): cover topic-name cache reload

* docs(changelog): note telegram topic-name persistence

* fix(telegram): allow topic cache without session runtime

* fix(telegram): persist topic cache via default runtime

* fix: move telegram topic-cache changelog to unreleased (openclaw#66107)

* test: enforce npm pack budget in install smoke

* fix: restore pnpm check

* test: remove timer dependency from telegram topic cache tests

* fix: avoid inline dotenv secrets in systemd unit during service repair (openclaw#66249) (thanks @tmimmanuel)

* fix(daemon): avoid inline dotenv secrets in systemd unit during service repair

* fix(daemon): sanitize systemd envfile and dedupe state-dir resolution

* fix(daemon): fail on multiline dotenv values for systemd envfile

* test(daemon): cover systemd envfile staging

* fix: keep systemd envfile overrides intact (openclaw#66249) (thanks @tmimmanuel)

---------

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

* test: cover gateway wake startup gating

* test: stabilize gateway wake gating regression

* fix: tighten inbound replay typing

* test: align feishu replay helper typing

* test: update model fallback auth store mock

* test: refresh cron and mcp typed fixtures

* test: use cron embedded runtime mock

* test: mock model fallback source check

* fix: align latest main type drift

* fix: remove agent config lint suppression

* test: align cron runtime seams

* test: align agent session resolver mocks

* test: align cron model error expectations

* test: drop removed agent scope suppression

* fix: keep baileys plugin-local

* test: align post-rebase full-suite drift

* fix: mirror baileys root dependency

* test: bound docker fs bridge probes

* test: keep telegram cache boundary compatible

* fix: cache external plugin catalog lookups in auto-enable (openclaw#66246) (thanks @yfge)

* fix: cache external plugin catalog lookups in auto-enable

Fixes openclaw#66159

* test: restore readFileSync spy in plugin auto-enable test

* refactor: distill plugin auto-enable cache path

* fix: cache external plugin catalog lookups in auto-enable (openclaw#66246) (thanks @yfge)

---------

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

* Agents: clarify local model context preflight (openclaw#66236)

Merged via squash.

Prepared head SHA: 11bfaf1
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Co-authored-by: ImLukeF <92253590+ImLukeF@users.noreply.github.com>
Reviewed-by: @ImLukeF

* fix: harden approvals get timeout handling (openclaw#66239) (thanks @neeravmakwana)

* fix(cli): harden approvals get timeout handling

* fix(cli): sanitize approvals timeout notes

* fix(cli): distill approvals timeout note

---------

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

* fix: honor configured store limits (openclaw#66240) (thanks @neeravmakwana)

* fix(media): honor configured store limits

* fix(media): report effective source limits

* refactor(media): distill configured limit wiring

---------

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

* fix(browser): relax default hostname SSRF guard

* fix(browser): use loopback policy for json-new fallback

* fix(browser): preserve explicit strict SSRF config

* fix: add browser SSRF follow-up changelog entry (openclaw#66386)

* fix(browser): preserve legacy strict SSRF alias

* feat(active-memory): instrument embedded runs

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: Bob <dutifulbob@gmail.com>
Co-authored-by: dutifulbob <261991368+dutifulbob@users.noreply.github.com>
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: mazhe-nerd <106217973+mazhe-nerd@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Minijus-Sa <minijus.savickas@hostinger.com>
Co-authored-by: EVA <admin@100yen.org>
Co-authored-by: 100yenadmin <239388517+100yenadmin@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Brian <95547369+zhuisDEV@users.noreply.github.com>
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
Co-authored-by: Mariano <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: Mariano Belinky <mariano@mb-server-643.local>
Co-authored-by: Omar Shahine <omarshahine@users.noreply.github.com>
Co-authored-by: Lobster <lobster@shahine.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ptah.ai <11701+ptahdunbar@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Pavan Kumar Gondhi <pgondhi@nvidia.com>
Co-authored-by: pashpashpash <nik@vault77.ai>
Co-authored-by: 屈定 <niudear@foxmail.com>
Co-authored-by: 屈定 <mrdear@users.noreply.github.com>
Co-authored-by: Altay <altay@uinaf.dev>
Co-authored-by: Agustin Rivera <31522568+eleqtrizit@users.noreply.github.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: rafaelreis-r <57492577+rafaelreis-r@users.noreply.github.com>
Co-authored-by: Byron <loong0306@163.com>
Co-authored-by: loong0306 <loong0306@gmail.com>
Co-authored-by: Nova <nova@openknot.ai>
Co-authored-by: Val Alexander <bunsthedev@gmail.com>
Co-authored-by: zhangfan49 <zhangfan49@baidu.com>
Co-authored-by: mbelinky <mbelinky@users.noreply.github.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
Co-authored-by: Xiaoshuai Zhang <i@jetd.one>
Co-authored-by: jetd1 <15795935+jetd1@users.noreply.github.com>
Co-authored-by: grp06 <1573959+grp06@users.noreply.github.com>
Co-authored-by: Josh Lehman <josh@martian.engineering>
Co-authored-by: ShihChi Huang <shh@theonlyperson.com>
Co-authored-by: @zimeg <zim@o526.net>
Co-authored-by: George Pickett <gpickett00@gmail.com>
Co-authored-by: Subash Natarajan <suboss87@gmail.com>
Co-authored-by: Joe LaPenna <jlapenna@gmail.com>
Co-authored-by: ly85206559 <ly85206559@163.com>
Co-authored-by: Eva H <63033505+hoyyeva@users.noreply.github.com>
Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com>
Co-authored-by: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com>
Co-authored-by: 拐爷&&老拐瘦 <geyunfei@gmail.com>
Co-authored-by: Luke <92253590+ImLukeF@users.noreply.github.com>
Co-authored-by: Neerav Makwana <neeravmakwana@gmail.com>
Co-authored-by: Bastion <bastion@agents.angventures.io>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…AI] (openclaw#66033)

* fix: address issue

* fix: address PR review feedback

* docs: add changelog entry for PR merge
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…AI] (openclaw#66033)

* fix: address issue

* fix: address PR review feedback

* docs: add changelog entry for PR merge
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…AI] (openclaw#66033)

* fix: address issue

* fix: address PR review feedback

* docs: add changelog entry for PR merge
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants