diff --git a/CHANGELOG.md b/CHANGELOG.md index be6957c430f..cab361b79e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai - Models status/OAuth health: align OAuth health reporting with the same effective credential view runtime uses, so expired refreshable sessions stop showing healthy by default and fresher imported Codex CLI credentials surface correctly in `models status`, doctor, and gateway auth status. Thanks @vincentkoc. - Twitch/setup: load Twitch through the bundled setup-entry discovery path and keep setup/status account detection aligned with runtime config. (#68008) Thanks @gumadeiras. - Feishu/card actions: resolve card-action chat type from the Feishu chat API when stored context is missing, preferring `chat_mode` over `chat_type`, so DM-originated card actions no longer bypass `dmPolicy` by falling through to the group handling path. (#68201) +- Cron/isolated-agent: preserve `trusted: false` on isolated cron awareness events mirrored into the main session, and forward the optional `trusted` flag through the gateway cron wrapper so explicit trust downgrades survive session-key scoping. (#68210) ## 2026.4.15 diff --git a/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts b/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts index 52dc888b592..f3caf6f7428 100644 --- a/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts +++ b/src/cron/isolated-agent/delivery-dispatch.double-announce.test.ts @@ -324,6 +324,7 @@ describe("dispatchCronDelivery — double-announce guard", () => { expect(enqueueSystemEvent).toHaveBeenCalledWith("Morning briefing complete.", { sessionKey: "agent:main:main", contextKey: "cron-direct-delivery:v1:run-123:telegram::123456:", + trusted: false, }); }); diff --git a/src/cron/isolated-agent/delivery-dispatch.ts b/src/cron/isolated-agent/delivery-dispatch.ts index 687bebfbac8..c53a6fca0c6 100644 --- a/src/cron/isolated-agent/delivery-dispatch.ts +++ b/src/cron/isolated-agent/delivery-dispatch.ts @@ -351,6 +351,7 @@ async function queueCronAwarenessSystemEvent(params: { agentId: params.agentId, }), contextKey: params.deliveryIdempotencyKey, + trusted: false, }); } catch (err) { await logCronDeliveryWarn( diff --git a/src/cron/service/state.ts b/src/cron/service/state.ts index 073efd8f459..f57073fbf0e 100644 --- a/src/cron/service/state.ts +++ b/src/cron/service/state.ts @@ -62,7 +62,7 @@ export type CronServiceDeps = { maxMissedJobsPerRestart?: number; enqueueSystemEvent: ( text: string, - opts?: { agentId?: string; sessionKey?: string; contextKey?: string }, + opts?: { agentId?: string; sessionKey?: string; contextKey?: string; trusted?: boolean }, ) => void; requestHeartbeatNow: (opts?: { reason?: string; agentId?: string; sessionKey?: string }) => void; runHeartbeatOnce?: (opts?: { diff --git a/src/gateway/server-cron.test.ts b/src/gateway/server-cron.test.ts index 28e048732de..c28a8f2bd41 100644 --- a/src/gateway/server-cron.test.ts +++ b/src/gateway/server-cron.test.ts @@ -140,6 +140,47 @@ describe("buildGatewayCronService", () => { } }); + it("preserves trust downgrades when cron enqueues system events", () => { + const cfg = createCronConfig("server-cron-untrusted"); + loadConfigMock.mockReturnValue(cfg); + + const state = buildGatewayCronService({ + cfg, + deps: {} as CliDeps, + broadcast: () => {}, + }); + try { + const cronDeps = ( + state.cron as unknown as { + state?: { + deps?: { + enqueueSystemEvent?: (optsText: string, opts?: { + agentId?: string; + sessionKey?: string; + contextKey?: string; + trusted?: boolean; + }) => void; + }; + }; + } + ).state?.deps; + + cronDeps?.enqueueSystemEvent?.("hello", { + sessionKey: "discord:channel:ops", + contextKey: "cron:test", + trusted: false, + }); + + expect(enqueueSystemEventMock).toHaveBeenCalledWith("hello", { + sessionKey: "agent:main:discord:channel:ops", + contextKey: "cron:test", + trusted: false, + }); + } finally { + state.cron.stop(); + } + }); + it("blocks private webhook URLs via SSRF-guarded fetch", async () => { const cfg = createCronConfig("server-cron-ssrf"); loadConfigMock.mockReturnValue(cfg); diff --git a/src/gateway/server-cron.ts b/src/gateway/server-cron.ts index 0d9b49a8600..f73289facd1 100644 --- a/src/gateway/server-cron.ts +++ b/src/gateway/server-cron.ts @@ -285,7 +285,11 @@ export function buildGatewayCronService(params: { agentId, requestedSessionKey: opts?.sessionKey, }); - enqueueSystemEvent(text, { sessionKey, contextKey: opts?.contextKey }); + enqueueSystemEvent(text, { + sessionKey, + contextKey: opts?.contextKey, + trusted: opts?.trusted, + }); }, requestHeartbeatNow: (opts) => { const { agentId, sessionKey } = resolveCronWakeTarget(opts);