High severity8.1NVD Advisory· Published Apr 10, 2026· Updated Apr 13, 2026
CVE-2026-35660
CVE-2026-35660
Description
OpenClaw before 2026.3.23 contains an insufficient access control vulnerability in the Gateway agent /reset endpoint that allows callers with operator.write permission to reset admin sessions. Attackers with operator.write privileges can invoke /reset or /new messages with an explicit sessionKey to bypass operator.admin requirements and reset arbitrary sessions.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.3.23 | 2026.3.23 |
Affected products
1Patches
250f6a2f136fefix(gateway): require admin for agent session reset
3 files changed · +54 −10
src/gateway/server.agent.gateway-server-agent-b.test.ts+5 −8 modified@@ -326,7 +326,7 @@ describe("gateway server agent", () => { expect(call.sessionId).not.toBe("sess-main-before-reset"); }); - test("write-scoped callers cannot use sessions.reset directly but can still reset conversations via agent", async () => { + test("write-scoped callers cannot reset conversations via agent", async () => { await withGatewayServer(async ({ port }) => { await useTempSessionStorePath(); const storePath = testState.sessionStorePath; @@ -358,19 +358,16 @@ describe("gateway server agent", () => { sessionKey: "main", idempotencyKey: "idem-agent-write-reset", }); - expect(viaAgent.ok).toBe(true); + expect(viaAgent.ok).toBe(false); + expect(viaAgent.error?.message).toContain("missing scope: operator.admin"); const store = JSON.parse(await fs.readFile(storePath, "utf-8")) as Record< string, { sessionId?: string } >; expect(store["agent:main:main"]?.sessionId).toBeDefined(); - expect(store["agent:main:main"]?.sessionId).not.toBe("sess-main-before-write-reset"); - - await vi.waitFor(() => expect(vi.mocked(agentCommand)).toHaveBeenCalled()); - const call = readAgentCommandCall(); - expect(typeof call.sessionId).toBe("string"); - expect(call.sessionId).not.toBe("sess-main-before-write-reset"); + expect(store["agent:main:main"]?.sessionId).toBe("sess-main-before-write-reset"); + expect(vi.mocked(agentCommand)).not.toHaveBeenCalled(); writeWs.close(); });
src/gateway/server-methods/agent.test.ts+36 −2 modified@@ -803,7 +803,10 @@ describe("gateway agent handler", () => { sessionKey: "agent:main:main", idempotencyKey: "test-idem-new", }, - { reqId: "4" }, + { + reqId: "4", + client: { connect: { scopes: ["operator.admin"] } } as AgentHandlerArgs["client"], + }, ); await waitForAssertion(() => expect(mocks.agentCommand).toHaveBeenCalled()); @@ -831,7 +834,10 @@ describe("gateway agent handler", () => { sessionKey: "agent:main:main", idempotencyKey: "test-idem-reset-suffix", }, - { reqId: "4b" }, + { + reqId: "4b", + client: { connect: { scopes: ["operator.admin"] } } as AgentHandlerArgs["client"], + }, ); const call = await expectResetCall("[Wed 2026-01-28 20:30 EST] check status"); @@ -861,6 +867,34 @@ describe("gateway agent handler", () => { ); }); + it("rejects /reset for write-scoped gateway callers", async () => { + mockMainSessionEntry({ sessionId: "existing-session-id" }); + mocks.performGatewaySessionReset.mockClear(); + mocks.agentCommand.mockClear(); + + const respond = await invokeAgent( + { + message: "/reset", + sessionKey: "agent:main:main", + idempotencyKey: "test-reset-write-scope", + }, + { + reqId: "4c", + client: { connect: { scopes: ["operator.write"] } } as AgentHandlerArgs["client"], + }, + ); + + expect(mocks.performGatewaySessionReset).not.toHaveBeenCalled(); + expect(mocks.agentCommand).not.toHaveBeenCalled(); + expect(respond).toHaveBeenCalledWith( + false, + undefined, + expect.objectContaining({ + message: "missing scope: operator.admin", + }), + ); + }); + it("rejects malformed session keys in agent.identity.get", async () => { const respond = await invokeAgentIdentityGet( {
src/gateway/server-methods/agent.ts+13 −0 modified@@ -79,6 +79,10 @@ function resolveAllowModelOverrideFromClient( return resolveSenderIsOwnerFromClient(client) || client?.internal?.allowModelOverride === true; } +function resolveCanResetSessionFromClient(client: GatewayRequestHandlerOptions["client"]): boolean { + return resolveSenderIsOwnerFromClient(client); +} + async function runSessionResetFromAgent(params: { key: string; reason: "new" | "reset"; @@ -240,6 +244,7 @@ export const agentHandlers: GatewayRequestHandlers = { }; const senderIsOwner = resolveSenderIsOwnerFromClient(client); const allowModelOverride = resolveAllowModelOverrideFromClient(client); + const canResetSession = resolveCanResetSessionFromClient(client); const requestedModelOverride = Boolean(request.provider || request.model); if (requestedModelOverride && !allowModelOverride) { respond( @@ -378,6 +383,14 @@ export const agentHandlers: GatewayRequestHandlers = { const resetCommandMatch = message.match(RESET_COMMAND_RE); if (resetCommandMatch && requestedSessionKey) { + if (!canResetSession) { + respond( + false, + undefined, + errorShape(ErrorCodes.INVALID_REQUEST, `missing scope: ${ADMIN_SCOPE}`), + ); + return; + } const resetReason = resetCommandMatch[1]?.toLowerCase() === "new" ? "new" : "reset"; const resetResult = await runSessionResetFromAgent({ key: requestedSessionKey,
630f1479c44fhttps://github.com/openclaw/openclawvia nvd-ref
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/openclaw/openclaw/commit/50f6a2f136fed85b58548a38f7a3dbb98d2cd1a0nvdPatchWEB
- github.com/openclaw/openclaw/commit/630f1479c44f78484dfa21bb407cbe6f171dac87nvdPatch
- github.com/advisories/GHSA-wq58-2pvg-5h4fghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-wq58-2pvg-5h4fnvdVendor AdvisoryWEB
- www.vulncheck.com/advisories/openclaw-insufficient-access-control-in-gateway-agent-session-resetnvdThird Party Advisory
News mentions
0No linked articles in our index yet.