VYPR
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.

PackageAffected versionsPatched versions
openclawnpm
< 2026.3.232026.3.23

Affected products

1
  • cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*
    Range: <2026.3.23

Patches

2
50f6a2f136fe

fix(gateway): require admin for agent session reset

https://github.com/openclaw/openclawVincent KocMar 23, 2026via ghsa
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,
    

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

News mentions

0

No linked articles in our index yet.