High severity8.8NVD Advisory· Published Apr 10, 2026· Updated Apr 13, 2026
CVE-2026-35669
CVE-2026-35669
Description
OpenClaw before 2026.3.25 contains a privilege escalation vulnerability in gateway-authenticated plugin HTTP routes that incorrectly mint operator.admin runtime scope regardless of caller-granted scopes. Attackers can exploit this scope boundary bypass to gain elevated privileges and perform unauthorized administrative actions.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | <= 2026.3.24 | — |
Affected products
1Patches
1ec2dbcff9afdfix: keep plugin HTTP runtime scopes least-privileged (#55284)
2 files changed · +55 −15
src/gateway/server/plugins-http.test.ts+49 −0 modified@@ -192,6 +192,55 @@ describe("createGatewayPluginRequestHandler", () => { expect(log.warn).toHaveBeenCalledWith(expect.stringContaining("missing scope: operator.admin")); }); + it("keeps gateway-authenticated plugin routes on least-privilege runtime scopes", async () => { + loadOpenClawPlugins.mockReset(); + handleGatewayRequest.mockReset(); + handleGatewayRequest.mockImplementation(async (opts: HandleGatewayRequestOptions) => { + const scopes = opts.client?.connect.scopes ?? []; + if (opts.req.method === "sessions.delete" && !scopes.includes("operator.admin")) { + opts.respond(false, undefined, { + code: "invalid_request", + message: "missing scope: operator.admin", + }); + return; + } + opts.respond(true, {}); + }); + + const subagent = await createSubagentRuntime(); + const log = createPluginLog(); + const handler = createGatewayPluginRequestHandler({ + registry: createTestRegistry({ + httpRoutes: [ + createRoute({ + path: "/secure-hook", + auth: "gateway", + handler: async (_req, _res) => { + await subagent.deleteSession({ sessionKey: "agent:main:subagent:child" }); + return true; + }, + }), + ], + }), + log, + }); + + const { res, setHeader, end } = makeMockHttpResponse(); + const handled = await handler({ url: "/secure-hook" } as IncomingMessage, res, undefined, { + gatewayAuthSatisfied: true, + }); + + expect(handled).toBe(true); + expect(handleGatewayRequest).toHaveBeenCalledTimes(1); + expect(handleGatewayRequest.mock.calls[0]?.[0]?.client?.connect.scopes).toEqual([ + "operator.write", + ]); + expect(res.statusCode).toBe(500); + expect(setHeader).toHaveBeenCalledWith("Content-Type", "text/plain; charset=utf-8"); + expect(end).toHaveBeenCalledWith("Internal Server Error"); + expect(log.warn).toHaveBeenCalledWith(expect.stringContaining("missing scope: operator.admin")); + }); + it("returns false when no routes are registered", async () => { const log = createPluginLog(); const handler = createGatewayPluginRequestHandler({
src/gateway/server/plugins-http.ts+6 −15 modified@@ -3,7 +3,7 @@ import type { createSubsystemLogger } from "../../logging/subsystem.js"; import type { PluginRegistry } from "../../plugins/registry.js"; import { resolveActivePluginHttpRouteRegistry } from "../../plugins/runtime.js"; import { withPluginRuntimeGatewayRequestScope } from "../../plugins/runtime/gateway-request-scope.js"; -import { ADMIN_SCOPE, APPROVALS_SCOPE, PAIRING_SCOPE, WRITE_SCOPE } from "../method-scopes.js"; +import { WRITE_SCOPE } from "../method-scopes.js"; import { GATEWAY_CLIENT_IDS, GATEWAY_CLIENT_MODES } from "../protocol/client-info.js"; import { PROTOCOL_VERSION } from "../protocol/index.js"; import type { GatewayRequestOptions } from "../server-methods/types.js"; @@ -27,16 +27,10 @@ export { shouldEnforceGatewayAuthForPluginPath } from "./plugins-http/route-auth type SubsystemLogger = ReturnType<typeof createSubsystemLogger>; -function createPluginRouteRuntimeClient(params: { - requiresGatewayAuth: boolean; - gatewayAuthSatisfied?: boolean; -}): GatewayRequestOptions["client"] { - // Plugin-authenticated webhooks can still use non-admin subagent helpers, - // but they must not inherit admin-only gateway methods by default. - const scopes = - params.requiresGatewayAuth && params.gatewayAuthSatisfied !== false - ? [ADMIN_SCOPE, APPROVALS_SCOPE, PAIRING_SCOPE] - : [WRITE_SCOPE]; +function createPluginRouteRuntimeClient(): GatewayRequestOptions["client"] { + // Plugin HTTP handlers only need the least-privilege runtime scope. + // Gateway route auth controls request admission, not runtime admin elevation. + const scopes = [WRITE_SCOPE]; return { connect: { minProtocol: PROTOCOL_VERSION, @@ -87,10 +81,7 @@ export function createGatewayPluginRequestHandler(params: { log.warn(`plugin http route blocked without gateway auth (${pathContext.canonicalPath})`); return false; } - const runtimeClient = createPluginRouteRuntimeClient({ - requiresGatewayAuth, - gatewayAuthSatisfied: dispatchContext?.gatewayAuthSatisfied, - }); + const runtimeClient = createPluginRouteRuntimeClient(); return await withPluginRuntimeGatewayRequestScope( {
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
4- github.com/openclaw/openclaw/commit/ec2dbcff9afd8a52e00de054b506c91726d9fbbenvdPatchWEB
- github.com/advisories/GHSA-qm2m-28pf-hgjwghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-qm2m-28pf-hgjwnvdVendor AdvisoryWEB
- www.vulncheck.com/advisories/openclaw-privilege-escalation-via-gateway-plugin-http-authentication-scopenvdThird Party Advisory
News mentions
0No linked articles in our index yet.