mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:30:43 +00:00
fix(gateway): block webchat session compaction mutations (#70716)
* fix(gateway): block webchat session compaction mutations * docs(changelog): note webchat compaction guard (#70716)
This commit is contained in:
@@ -241,6 +241,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
## 2026.4.21
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ function emitSessionsChanged(
|
||||
}
|
||||
|
||||
function rejectWebchatSessionMutation(params: {
|
||||
action: "patch" | "delete";
|
||||
action: "patch" | "delete" | "compact" | "restore";
|
||||
client: GatewayClient | null;
|
||||
isWebchatConnect: (params: GatewayClient["connect"] | null | undefined) => boolean;
|
||||
respond: RespondFn;
|
||||
@@ -1101,6 +1101,9 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (rejectWebchatSessionMutation({ action: "restore", client, isWebchatConnect, respond })) {
|
||||
return;
|
||||
}
|
||||
const checkpointId =
|
||||
typeof p.checkpointId === "string" && p.checkpointId.trim() ? p.checkpointId.trim() : "";
|
||||
if (!checkpointId) {
|
||||
@@ -1495,6 +1498,9 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
if (rejectWebchatSessionMutation({ action: "compact", client, isWebchatConnect, respond })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const maxLines =
|
||||
typeof p.maxLines === "number" && Number.isFinite(p.maxLines)
|
||||
|
||||
@@ -3329,14 +3329,40 @@ describe("gateway server sessions", () => {
|
||||
ws.close();
|
||||
});
|
||||
|
||||
test("webchat clients cannot patch or delete sessions", async () => {
|
||||
await createSessionStoreDir();
|
||||
test("webchat clients cannot patch, delete, compact, or restore sessions", async () => {
|
||||
const { dir } = await createSessionStoreDir();
|
||||
const fixture = await createCheckpointFixture(dir);
|
||||
|
||||
await writeSessionStore({
|
||||
entries: {
|
||||
main: {
|
||||
sessionId: "sess-main",
|
||||
sessionId: fixture.sessionId,
|
||||
sessionFile: fixture.sessionFile,
|
||||
updatedAt: Date.now(),
|
||||
compactionCheckpoints: [
|
||||
{
|
||||
checkpointId: "checkpoint-1",
|
||||
sessionKey: "agent:main:main",
|
||||
sessionId: fixture.sessionId,
|
||||
createdAt: Date.now(),
|
||||
reason: "manual",
|
||||
tokensBefore: 123,
|
||||
tokensAfter: 45,
|
||||
summary: "checkpoint summary",
|
||||
firstKeptEntryId: fixture.preCompactionLeafId,
|
||||
preCompaction: {
|
||||
sessionId: fixture.preCompactionSession.getSessionId(),
|
||||
sessionFile: fixture.preCompactionSessionFile,
|
||||
leafId: fixture.preCompactionLeafId,
|
||||
},
|
||||
postCompaction: {
|
||||
sessionId: fixture.sessionId,
|
||||
sessionFile: fixture.sessionFile,
|
||||
leafId: fixture.postCompactionLeafId,
|
||||
entryId: fixture.postCompactionLeafId,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"discord:group:dev": {
|
||||
sessionId: "sess-group",
|
||||
@@ -3373,6 +3399,20 @@ describe("gateway server sessions", () => {
|
||||
expect(deleted.ok).toBe(false);
|
||||
expect(deleted.error?.message ?? "").toMatch(/webchat clients cannot delete sessions/i);
|
||||
|
||||
const compacted = await rpcReq(ws, "sessions.compact", {
|
||||
key: "main",
|
||||
maxLines: 3,
|
||||
});
|
||||
expect(compacted.ok).toBe(false);
|
||||
expect(compacted.error?.message ?? "").toMatch(/webchat clients cannot compact sessions/i);
|
||||
|
||||
const restored = await rpcReq(ws, "sessions.compaction.restore", {
|
||||
key: "main",
|
||||
checkpointId: "checkpoint-1",
|
||||
});
|
||||
expect(restored.ok).toBe(false);
|
||||
expect(restored.error?.message ?? "").toMatch(/webchat clients cannot restore sessions/i);
|
||||
|
||||
ws.close();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user