diff --git a/CHANGELOG.md b/CHANGELOG.md index 259ffadfb..d3734343c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Docs: https://docs.openclaw.ai - Config/raw redaction safety: preserve non-sensitive literals during raw redaction round-trips, scope SecretRef redaction to secret IDs (not structural fields like `source`/`provider`), and fall back to structured raw redaction when text replacement cannot restore the original config shape. (#32174) Thanks @bmendonca3. - Models/Codex usage labels: infer weekly secondary usage windows from reset cadence when API window seconds are ambiguously reported as 24h, so `openclaw models status` no longer mislabels weekly limits as daily. (#31938) Thanks @bmendonca3. - Config/backups hardening: enforce owner-only (`0600`) permissions on rotated config backups and clean orphan `.bak.*` files outside the managed backup ring, reducing credential leakage risk from stale or permissive backup artifacts. (#31718) Thanks @YUJIE2002. +- Tests/Windows backup rotation: skip chmod-only backup permission assertions on Windows while retaining compose/rotation/prune coverage across platforms to avoid false CI failures from Windows non-POSIX mode semantics. (#32286) Thanks @jalehman. - WhatsApp/inbound self-message context: propagate inbound `fromMe` through the web inbox pipeline and annotate direct self messages as `(self)` in envelopes so agents can distinguish owner-authored turns from contact turns. (#32167) Thanks @scoootscooob. - Webchat/silent token leak: filter assistant `NO_REPLY`-only transcript entries from `chat.history` responses and add client-side defense-in-depth guards in the chat controller so internal silent tokens never render as visible chat bubbles. (#32015) Consolidates overlap from #32183, #32082, #32045, #32052, #32172, and #32112. Thanks @ademczuk, @liuxiaopai-ai, @ningding97, @bmendonca3, and @x4v13r1120. - Exec approvals/allowlist matching: escape regex metacharacters in path-pattern literals (while preserving glob wildcards), preventing crashes on allowlisted executables like `/usr/bin/g++` and correctly matching mixed wildcard/literal token paths. (#32162) Thanks @stakeswky. diff --git a/src/config/config.backup-rotation.test.ts b/src/config/config.backup-rotation.test.ts index db8cbfe3b..b89f06533 100644 --- a/src/config/config.backup-rotation.test.ts +++ b/src/config/config.backup-rotation.test.ts @@ -116,34 +116,33 @@ describe("config backup rotation", () => { }); }); - // chmod is a no-op on Windows — permission assertions will always fail. - it.skipIf(IS_WINDOWS)( - "maintainConfigBackups composes rotate/copy/harden/prune flow", - async () => { - await withTempHome(async () => { - const stateDir = process.env.OPENCLAW_STATE_DIR?.trim(); - if (!stateDir) { - throw new Error("Expected OPENCLAW_STATE_DIR to be set by withTempHome"); - } - const configPath = path.join(stateDir, "openclaw.json"); - await fs.writeFile(configPath, JSON.stringify({ token: "secret" }), { mode: 0o600 }); - await fs.writeFile(`${configPath}.bak`, "previous", { mode: 0o644 }); - await fs.writeFile(`${configPath}.bak.orphan`, "old"); + it("maintainConfigBackups composes rotate/copy/harden/prune flow", async () => { + await withTempHome(async () => { + const stateDir = process.env.OPENCLAW_STATE_DIR?.trim(); + if (!stateDir) { + throw new Error("Expected OPENCLAW_STATE_DIR to be set by withTempHome"); + } + const configPath = path.join(stateDir, "openclaw.json"); + await fs.writeFile(configPath, JSON.stringify({ token: "secret" }), { mode: 0o600 }); + await fs.writeFile(`${configPath}.bak`, "previous", { mode: 0o644 }); + await fs.writeFile(`${configPath}.bak.orphan`, "old"); - await maintainConfigBackups(configPath, fs); + await maintainConfigBackups(configPath, fs); - // A new primary backup is created from the current config. - await expect(fs.readFile(`${configPath}.bak`, "utf-8")).resolves.toBe( - JSON.stringify({ token: "secret" }), - ); - // Prior primary backup gets rotated into ring slot 1. - await expect(fs.readFile(`${configPath}.bak.1`, "utf-8")).resolves.toBe("previous"); - // Mode hardening still applies. + // A new primary backup is created from the current config. + await expect(fs.readFile(`${configPath}.bak`, "utf-8")).resolves.toBe( + JSON.stringify({ token: "secret" }), + ); + // Prior primary backup gets rotated into ring slot 1. + await expect(fs.readFile(`${configPath}.bak.1`, "utf-8")).resolves.toBe("previous"); + // Windows cannot validate POSIX chmod bits, but all other compose assertions + // should still run there. + if (!IS_WINDOWS) { const primaryBackupStat = await fs.stat(`${configPath}.bak`); expect(primaryBackupStat.mode & 0o777).toBe(0o600); - // Out-of-ring orphan gets pruned. - await expect(fs.stat(`${configPath}.bak.orphan`)).rejects.toThrow(); - }); - }, - ); + } + // Out-of-ring orphan gets pruned. + await expect(fs.stat(`${configPath}.bak.orphan`)).rejects.toThrow(); + }); + }); });