Skip to content

Commit 873dce1

Browse files
authored
fix(gateway): block webchat session compaction mutations (#70716)
* fix(gateway): block webchat session compaction mutations * docs(changelog): note webchat compaction guard (#70716)
1 parent 8af3d91 commit 873dce1

3 files changed

Lines changed: 51 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ Docs: https://docs.openclaw.ai
241241
- Gateway/Control UI: require authenticated Control UI read access before serving `/__openclaw/control-ui-config.json` when `gateway.auth` is enabled, so unauthenticated callers can no longer read bootstrap metadata. (#70247) Thanks @drobison00.
242242
- Gateway/restart: default session-scoped restart sentinels to a one-shot agent continuation, so chat-initiated Gateway restarts acknowledge successful boot automatically. (#70269) Thanks @obviyus.
243243
- Build/npm publish: fail postpublish verification when root `dist/*` files import bundled plugin runtime dependencies without mirroring them in the root package manifest, so Slack-style plugin deps cannot silently ship on the wrong module-resolution path again. (#60112) thanks @medns.
244+
- Gateway/sessions: extend the webchat session-mutation guard to `sessions.compact` and `sessions.compaction.restore`, so `WEBCHAT_UI` clients are rejected from compaction-side session mutations consistently with the existing patch/delete guards. (#70716) Thanks @drobison00.
244245

245246
## 2026.4.21
246247

src/gateway/server-methods/sessions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ function emitSessionsChanged(
223223
}
224224

225225
function rejectWebchatSessionMutation(params: {
226-
action: "patch" | "delete";
226+
action: "patch" | "delete" | "compact" | "restore";
227227
client: GatewayClient | null;
228228
isWebchatConnect: (params: GatewayClient["connect"] | null | undefined) => boolean;
229229
respond: RespondFn;
@@ -1101,6 +1101,9 @@ export const sessionsHandlers: GatewayRequestHandlers = {
11011101
if (!key) {
11021102
return;
11031103
}
1104+
if (rejectWebchatSessionMutation({ action: "restore", client, isWebchatConnect, respond })) {
1105+
return;
1106+
}
11041107
const checkpointId =
11051108
typeof p.checkpointId === "string" && p.checkpointId.trim() ? p.checkpointId.trim() : "";
11061109
if (!checkpointId) {
@@ -1495,6 +1498,9 @@ export const sessionsHandlers: GatewayRequestHandlers = {
14951498
if (!key) {
14961499
return;
14971500
}
1501+
if (rejectWebchatSessionMutation({ action: "compact", client, isWebchatConnect, respond })) {
1502+
return;
1503+
}
14981504

14991505
const maxLines =
15001506
typeof p.maxLines === "number" && Number.isFinite(p.maxLines)

src/gateway/server.sessions.gateway-server-sessions-a.test.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3329,14 +3329,40 @@ describe("gateway server sessions", () => {
33293329
ws.close();
33303330
});
33313331

3332-
test("webchat clients cannot patch or delete sessions", async () => {
3333-
await createSessionStoreDir();
3332+
test("webchat clients cannot patch, delete, compact, or restore sessions", async () => {
3333+
const { dir } = await createSessionStoreDir();
3334+
const fixture = await createCheckpointFixture(dir);
33343335

33353336
await writeSessionStore({
33363337
entries: {
33373338
main: {
3338-
sessionId: "sess-main",
3339+
sessionId: fixture.sessionId,
3340+
sessionFile: fixture.sessionFile,
33393341
updatedAt: Date.now(),
3342+
compactionCheckpoints: [
3343+
{
3344+
checkpointId: "checkpoint-1",
3345+
sessionKey: "agent:main:main",
3346+
sessionId: fixture.sessionId,
3347+
createdAt: Date.now(),
3348+
reason: "manual",
3349+
tokensBefore: 123,
3350+
tokensAfter: 45,
3351+
summary: "checkpoint summary",
3352+
firstKeptEntryId: fixture.preCompactionLeafId,
3353+
preCompaction: {
3354+
sessionId: fixture.preCompactionSession.getSessionId(),
3355+
sessionFile: fixture.preCompactionSessionFile,
3356+
leafId: fixture.preCompactionLeafId,
3357+
},
3358+
postCompaction: {
3359+
sessionId: fixture.sessionId,
3360+
sessionFile: fixture.sessionFile,
3361+
leafId: fixture.postCompactionLeafId,
3362+
entryId: fixture.postCompactionLeafId,
3363+
},
3364+
},
3365+
],
33403366
},
33413367
"discord:group:dev": {
33423368
sessionId: "sess-group",
@@ -3373,6 +3399,20 @@ describe("gateway server sessions", () => {
33733399
expect(deleted.ok).toBe(false);
33743400
expect(deleted.error?.message ?? "").toMatch(/webchat clients cannot delete sessions/i);
33753401

3402+
const compacted = await rpcReq(ws, "sessions.compact", {
3403+
key: "main",
3404+
maxLines: 3,
3405+
});
3406+
expect(compacted.ok).toBe(false);
3407+
expect(compacted.error?.message ?? "").toMatch(/webchat clients cannot compact sessions/i);
3408+
3409+
const restored = await rpcReq(ws, "sessions.compaction.restore", {
3410+
key: "main",
3411+
checkpointId: "checkpoint-1",
3412+
});
3413+
expect(restored.ok).toBe(false);
3414+
expect(restored.error?.message ?? "").toMatch(/webchat clients cannot restore sessions/i);
3415+
33763416
ws.close();
33773417
});
33783418

0 commit comments

Comments
 (0)