From 9631f4665cea3919086100c30b559dca5a500b15 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 9 Mar 2026 06:31:35 +0000 Subject: [PATCH 1/4] chore: refresh secrets baseline --- .secrets.baseline | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 4f591aa13ef..871217bc3bc 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -12933,14 +12933,14 @@ "filename": "src/telegram/monitor.test.ts", "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", "is_verified": false, - "line_number": 450 + "line_number": 497 }, { "type": "Secret Keyword", "filename": "src/telegram/monitor.test.ts", "hashed_secret": "5934c4d4a4fa5d66ddb3d3fc0bba84996c17a5b7", "is_verified": false, - "line_number": 641 + "line_number": 688 } ], "src/telegram/webhook.test.ts": [ @@ -13035,5 +13035,5 @@ } ] }, - "generated_at": "2026-03-09T01:11:58Z" + "generated_at": "2026-03-09T06:30:58Z" } From 2d55ad05f397d9a20df0f677498f60094a59b749 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 9 Mar 2026 06:34:48 +0000 Subject: [PATCH 2/4] docs: move 2026.3.8 entries back to unreleased --- CHANGELOG.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9c445f8337..b930ee7514a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,6 @@ Docs: https://docs.openclaw.ai ## Unreleased -### Fixes - -- Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent. -- MS Teams/authz: keep `groupPolicy: "allowlist"` enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent. -- Security/system.run: bind approved `bun` and `deno run` script operands to on-disk file snapshots so post-approval script rewrites are denied before execution. -- Skills/download installs: pin the validated per-skill tools root before writing downloaded archives, so rebinding the lexical tools path cannot redirect download writes outside the intended tools directory. Thanks @tdjackey. - -## 2026.3.8 - ### Changes - CLI/backup: add `openclaw backup create` and `openclaw backup verify` for local state archives, including `--only-config`, `--no-include-workspace`, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs. @@ -72,6 +63,10 @@ Docs: https://docs.openclaw.ai - Telegram/poll restart cleanup: abort the in-flight Telegram API fetch when shutdown or forced polling restarts stop a runner, preventing stale `getUpdates` long polls from colliding with the replacement runner. Landed from contributor PR #23950 by @Gkinthecodeland. Thanks @Gkinthecodeland. - Cron/restart catch-up staggering: limit immediate missed-job replay on startup and reschedule the deferred remainder from the post-catchup clock so restart bursts do not starve the gateway or silently skip overdue recurring jobs. Landed from contributor PR #18925 by @rexlunae. Thanks @rexlunae. - Cron/owner-only tools: pass trusted isolated cron runs into the embedded agent with owner context so `cron`/`gateway` tooling remains available after the owner-auth hardening narrowed direct-message ownership inference. +- Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent. +- MS Teams/authz: keep `groupPolicy: "allowlist"` enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent. +- Security/system.run: bind approved `bun` and `deno run` script operands to on-disk file snapshots so post-approval script rewrites are denied before execution. +- Skills/download installs: pin the validated per-skill tools root before writing downloaded archives, so rebinding the lexical tools path cannot redirect download writes outside the intended tools directory. Thanks @tdjackey. ## 2026.3.7 From 8d2d6db9ada1dc1afe482cf7e52d4526c2cf5d6c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 9 Mar 2026 06:45:02 +0000 Subject: [PATCH 3/4] test: fix Node 24+ test runner and subagent registry mocks --- scripts/test-parallel.mjs | 6 +++--- src/agents/subagent-registry.archive.e2e.test.ts | 14 +++++++++----- ...gent-registry.lifecycle-retry-grace.e2e.test.ts | 10 +++++++--- src/agents/subagent-registry.nested.e2e.test.ts | 14 +++++++++----- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/scripts/test-parallel.mjs b/scripts/test-parallel.mjs index 67129a24aea..ca7636394bb 100644 --- a/scripts/test-parallel.mjs +++ b/scripts/test-parallel.mjs @@ -104,11 +104,11 @@ const hostMemoryGiB = Math.floor(os.totalmem() / 1024 ** 3); const highMemLocalHost = !isCI && hostMemoryGiB >= 96; const lowMemLocalHost = !isCI && hostMemoryGiB < 64; const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10); -// vmForks is a big win for transform/import heavy suites, but Node 24 had -// regressions with Vitest's vm runtime in this repo, and low-memory local hosts +// vmForks is a big win for transform/import heavy suites, but Node 24+ +// regressed with Vitest's vm runtime in this repo, and low-memory local hosts // are more likely to hit per-worker V8 heap ceilings. Keep it opt-out via // OPENCLAW_TEST_VM_FORKS=0, and let users force-enable with =1. -const supportsVmForks = Number.isFinite(nodeMajor) ? nodeMajor !== 24 : true; +const supportsVmForks = Number.isFinite(nodeMajor) ? nodeMajor < 24 : true; const useVmForks = process.env.OPENCLAW_TEST_VM_FORKS === "1" || (process.env.OPENCLAW_TEST_VM_FORKS !== "0" && !isWindows && supportsVmForks && !lowMemLocalHost); diff --git a/src/agents/subagent-registry.archive.e2e.test.ts b/src/agents/subagent-registry.archive.e2e.test.ts index 20148db527a..8cd2a9b634e 100644 --- a/src/agents/subagent-registry.archive.e2e.test.ts +++ b/src/agents/subagent-registry.archive.e2e.test.ts @@ -17,11 +17,15 @@ vi.mock("../infra/agent-events.js", () => ({ onAgentEvent: vi.fn((_handler: unknown) => noop), })); -vi.mock("../config/config.js", () => ({ - loadConfig: vi.fn(() => ({ - agents: { defaults: { subagents: { archiveAfterMinutes: 60 } } }, - })), -})); +vi.mock("../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: vi.fn(() => ({ + agents: { defaults: { subagents: { archiveAfterMinutes: 60 } } }, + })), + }; +}); vi.mock("./subagent-announce.js", () => ({ runSubagentAnnounceFlow: vi.fn(async () => true), diff --git a/src/agents/subagent-registry.lifecycle-retry-grace.e2e.test.ts b/src/agents/subagent-registry.lifecycle-retry-grace.e2e.test.ts index 9373ee5de64..570c51d3131 100644 --- a/src/agents/subagent-registry.lifecycle-retry-grace.e2e.test.ts +++ b/src/agents/subagent-registry.lifecycle-retry-grace.e2e.test.ts @@ -49,9 +49,13 @@ vi.mock("../infra/agent-events.js", () => ({ onAgentEvent: onAgentEventMock, })); -vi.mock("../config/config.js", () => ({ - loadConfig: loadConfigMock, -})); +vi.mock("../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: loadConfigMock, + }; +}); vi.mock("./subagent-announce.js", () => ({ runSubagentAnnounceFlow: announceSpy, diff --git a/src/agents/subagent-registry.nested.e2e.test.ts b/src/agents/subagent-registry.nested.e2e.test.ts index 30e447149c2..06148705986 100644 --- a/src/agents/subagent-registry.nested.e2e.test.ts +++ b/src/agents/subagent-registry.nested.e2e.test.ts @@ -1,11 +1,15 @@ import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import "./subagent-registry.mocks.shared.js"; -vi.mock("../config/config.js", () => ({ - loadConfig: vi.fn(() => ({ - agents: { defaults: { subagents: { archiveAfterMinutes: 0 } } }, - })), -})); +vi.mock("../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: vi.fn(() => ({ + agents: { defaults: { subagents: { archiveAfterMinutes: 0 } } }, + })), + }; +}); vi.mock("./subagent-announce.js", () => ({ runSubagentAnnounceFlow: vi.fn(async () => true), From 912aa8744aa83cc1fcead8058645cd06aa4bfe89 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 9 Mar 2026 06:50:52 +0000 Subject: [PATCH 4/4] test: fix Windows fake runtime bin fixtures --- src/node-host/invoke-system-run-plan.test.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/node-host/invoke-system-run-plan.test.ts b/src/node-host/invoke-system-run-plan.test.ts index 8b10bceb2c0..019eb7b77b9 100644 --- a/src/node-host/invoke-system-run-plan.test.ts +++ b/src/node-host/invoke-system-run-plan.test.ts @@ -69,9 +69,16 @@ function withFakeRuntimeBin(params: { binName: string; run: () => T }): T { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), `openclaw-${params.binName}-bin-`)); const binDir = path.join(tmp, "bin"); fs.mkdirSync(binDir, { recursive: true }); - const runtimePath = path.join(binDir, params.binName); - fs.writeFileSync(runtimePath, "#!/bin/sh\nexit 0\n", { mode: 0o755 }); - fs.chmodSync(runtimePath, 0o755); + const runtimePath = + process.platform === "win32" + ? path.join(binDir, `${params.binName}.cmd`) + : path.join(binDir, params.binName); + const runtimeBody = + process.platform === "win32" ? "@echo off\r\nexit /b 0\r\n" : "#!/bin/sh\nexit 0\n"; + fs.writeFileSync(runtimePath, runtimeBody, { mode: 0o755 }); + if (process.platform !== "win32") { + fs.chmodSync(runtimePath, 0o755); + } const oldPath = process.env.PATH; process.env.PATH = `${binDir}${path.delimiter}${oldPath ?? ""}`; try {