security(gateway): block webchat session mutators (#20800)

* chore(ci): local claude settings gitignore

* Gateway: block webchat session mutators

* Changelog: note webchat session mutator guard

* Changelog: credit report for webchat mutator guard
This commit is contained in:
Vincent Koc
2026-02-19 01:54:02 -08:00
committed by GitHub
parent 32ba62dc69
commit 981d266480
4 changed files with 76 additions and 2 deletions

View File

@@ -2,7 +2,9 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import { WebSocket } from "ws";
import { DEFAULT_PROVIDER } from "../agents/defaults.js";
import { GATEWAY_CLIENT_IDS, GATEWAY_CLIENT_MODES } from "./protocol/client-info.js";
import { startGatewayServerHarness, type GatewayServerHarness } from "./server.e2e-ws-harness.js";
import { createToolSummaryPreviewTranscriptLines } from "./session-preview.test-helpers.js";
import {
@@ -742,4 +744,52 @@ describe("gateway server sessions", () => {
ws.close();
});
test("webchat clients cannot patch or delete sessions", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-webchat-"));
const storePath = path.join(dir, "sessions.json");
testState.sessionStorePath = storePath;
await writeSessionStore({
entries: {
main: {
sessionId: "sess-main",
updatedAt: Date.now(),
},
"discord:group:dev": {
sessionId: "sess-group",
updatedAt: Date.now(),
},
},
});
const ws = new WebSocket(`ws://127.0.0.1:${harness.port}`, {
headers: { origin: `http://127.0.0.1:${harness.port}` },
});
await new Promise<void>((resolve) => ws.once("open", resolve));
await connectOk(ws, {
client: {
id: GATEWAY_CLIENT_IDS.WEBCHAT_UI,
version: "1.0.0",
platform: "test",
mode: GATEWAY_CLIENT_MODES.UI,
},
scopes: ["operator.admin"],
});
const patched = await rpcReq(ws, "sessions.patch", {
key: "agent:main:discord:group:dev",
label: "should-fail",
});
expect(patched.ok).toBe(false);
expect(patched.error?.message ?? "").toMatch(/webchat clients cannot patch sessions/i);
const deleted = await rpcReq(ws, "sessions.delete", {
key: "agent:main:discord:group:dev",
});
expect(deleted.ok).toBe(false);
expect(deleted.error?.message ?? "").toMatch(/webchat clients cannot delete sessions/i);
ws.close();
});
});