diff --git a/CHANGELOG.md b/CHANGELOG.md index e49d930be46..235981b8b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai - Skills/Linux: harden go installer fallback on apt-based systems by handling root/no-sudo environments safely, doing best-effort apt index refresh, and returning actionable errors instead of failing with spawn errors. (#17687) Thanks @mcrolly. - Web Fetch/Security: cap downloaded response body size before HTML parsing to prevent memory exhaustion from oversized or deeply nested pages. Thanks @xuemian168. - Config/Gateway: make sensitive-key whitelist suffix matching case-insensitive while preserving `passwordFile` path exemptions, preventing accidental redaction of non-secret config values like `maxTokens` and IRC password-file paths. (#16042) Thanks @akramcodez. +- Gateway/Config: prevent `config.patch` from falling back to full-array replacement when an object-array patch includes entries without `id`, so partial `agents.list` updates no longer risk deleting unrelated agents. (#17989) Thanks @stakeswky. - Dev tooling: harden git `pre-commit` hook against option injection from malicious filenames (for example `--force`), preventing accidental staging of ignored files. Thanks @mrthankyou. - Gateway/Agent: reject malformed `agent:`-prefixed session keys (for example, `agent:main`) in `agent` and `agent.identity.get` instead of silently resolving them to the default agent, preventing accidental cross-session routing. (#15707) Thanks @rodrigouroz. - Gateway/Chat: harden `chat.send` inbound message handling by rejecting null bytes, stripping unsafe control characters, and normalizing Unicode to NFC before dispatch. (#8593) Thanks @fr33d3m0n. diff --git a/src/gateway/server.config-patch.e2e.test.ts b/src/gateway/server.config-patch.e2e.test.ts index 4ea6f08810e..19fdd5ad376 100644 --- a/src/gateway/server.config-patch.e2e.test.ts +++ b/src/gateway/server.config-patch.e2e.test.ts @@ -91,6 +91,47 @@ describe("gateway config methods", () => { expect(primary?.workspace).toBe("/tmp/primary-updated"); expect(secondary?.workspace).toBe("/tmp/secondary"); }); + + it("rejects mixed-id agents.list patches without mutating persisted config", async () => { + const setRes = await rpcReq<{ ok?: boolean }>(ws, "config.set", { + raw: JSON.stringify({ + agents: { + list: [ + { id: "primary", default: true, workspace: "/tmp/primary" }, + { id: "secondary", workspace: "/tmp/secondary" }, + ], + }, + }), + }); + expect(setRes.ok).toBe(true); + + const beforeRes = await rpcReq<{ hash?: string }>(ws, "config.get", {}); + expect(beforeRes.ok).toBe(true); + expect(typeof beforeRes.payload?.hash).toBe("string"); + + const patchRes = await rpcReq<{ ok?: boolean }>(ws, "config.patch", { + baseHash: beforeRes.payload?.hash, + raw: JSON.stringify({ + agents: { + list: [ + { + id: "primary", + workspace: "/tmp/primary-updated", + }, + { + workspace: "/tmp/orphan-no-id", + }, + ], + }, + }), + }); + expect(patchRes.ok).toBe(false); + expect(patchRes.error?.message ?? "").toContain("invalid config"); + + const afterRes = await rpcReq<{ hash?: string }>(ws, "config.get", {}); + expect(afterRes.ok).toBe(true); + expect(afterRes.payload?.hash).toBe(beforeRes.payload?.hash); + }); }); describe("gateway server sessions", () => {