CVE-2026-53809
Description
OpenClaw before 2026.4.25 allows policy bypass via provider alias confusion in embedded runner policy, enabling unintended tool access.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
OpenClaw before 2026.4.25 allows policy bypass via provider alias confusion in embedded runner policy, enabling unintended tool access.
Vulnerability
OpenClaw before version 2026.4.25 contains a policy bypass vulnerability in the embedded runner policy [1][2]. The bug occurs when a request uses provider aliases; the policy comparison is performed against the alias instead of the canonical provider identity. This confusion allows selecting bundled tool access outside the intended provider policy restrictions when the affected feature is enabled [1].
Exploitation
An attacker requires the affected feature to be enabled and reachable [1]. The attacker must be an authenticated Gateway operator, as the vulnerability is scoped to that model and does not bypass the trusted-operator boundary [1]. By crafting a request that uses a provider alias, the attacker can cause the policy engine to compare against the alias rather than the canonical identity, leading to authorization confusion [2].
Impact
Successful exploitation can select bundled tool access outside the intended provider policy restrictions [1]. The practical impact depends on the operator's configuration and whether lower-trust input can reach that path [1]. This is primarily an integrity violation, as the attacker may gain unauthorized tool selection, but no confidentiality or availability impact is expected under typical conditions [2].
Mitigation
The first stable patched version is 2026.4.25 [1]. If patching is not immediately possible, operators should avoid provider alias routing for embedded runner tool policy until patched [1]. Additional hardening steps include keeping channel and tool allowlists narrow, avoiding sharing one Gateway between mutually untrusted users, and disabling the affected feature when it is not needed [1].
AI Insight generated on Jun 11, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1aa36ee670b76fix(gateway): stage startup plugin deps before load
2 files changed · +100 −13
src/gateway/server-startup-plugins.test.ts+22 −13 modified@@ -1,5 +1,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +type ConfiguredDeferredChannelPluginIdsResolver = + typeof import("../plugins/channel-plugin-ids.js").resolveConfiguredDeferredChannelPluginIds; +type GatewayStartupPluginIdsResolver = + typeof import("../plugins/channel-plugin-ids.js").resolveGatewayStartupPluginIds; + const applyPluginAutoEnable = vi.hoisted(() => vi.fn((params: { config: unknown }) => ({ config: params.config, @@ -14,14 +19,16 @@ const loadGatewayStartupPlugins = vi.hoisted(() => gatewayMethods: ["ping"], })), ); -const repairBundledRuntimeDepsInstallRootAsync = vi.hoisted(() => - vi.fn(async (_params: unknown) => ({})), -); +const repairBundledRuntimeDepsInstallRoot = vi.hoisted(() => vi.fn((_params: unknown) => ({}))); const resolveBundledRuntimeDependencyPackageInstallRoot = vi.hoisted(() => vi.fn((_packageRoot: string, _params: unknown) => "/runtime"), ); -const resolveConfiguredDeferredChannelPluginIds = vi.hoisted(() => vi.fn(() => [])); -const resolveGatewayStartupPluginIds = vi.hoisted(() => vi.fn(() => ["memory-core"])); +const resolveConfiguredDeferredChannelPluginIds = vi.hoisted(() => + vi.fn<ConfiguredDeferredChannelPluginIdsResolver>(() => []), +); +const resolveGatewayStartupPluginIds = vi.hoisted(() => + vi.fn<GatewayStartupPluginIdsResolver>(() => ["memory-core"]), +); const resolveOpenClawPackageRootSync = vi.hoisted(() => vi.fn((_params: unknown) => "/package")); const runChannelPluginStartupMaintenance = vi.hoisted(() => vi.fn(async (_params: unknown) => undefined), @@ -61,17 +68,19 @@ vi.mock("../infra/openclaw-root.js", () => ({ })); vi.mock("../plugins/bundled-runtime-deps.js", () => ({ - repairBundledRuntimeDepsInstallRootAsync: (params: unknown) => - repairBundledRuntimeDepsInstallRootAsync(params), + repairBundledRuntimeDepsInstallRoot: (params: unknown) => + repairBundledRuntimeDepsInstallRoot(params), resolveBundledRuntimeDependencyPackageInstallRoot: (packageRoot: string, params: unknown) => resolveBundledRuntimeDependencyPackageInstallRoot(packageRoot, params), scanBundledPluginRuntimeDeps: (params: unknown) => scanBundledPluginRuntimeDeps(params), })); vi.mock("../plugins/channel-plugin-ids.js", () => ({ - resolveConfiguredDeferredChannelPluginIds: (params: unknown) => - resolveConfiguredDeferredChannelPluginIds(params), - resolveGatewayStartupPluginIds: (params: unknown) => resolveGatewayStartupPluginIds(params), + resolveConfiguredDeferredChannelPluginIds: ( + ...args: Parameters<ConfiguredDeferredChannelPluginIdsResolver> + ) => resolveConfiguredDeferredChannelPluginIds(...args), + resolveGatewayStartupPluginIds: (...args: Parameters<GatewayStartupPluginIdsResolver>) => + resolveGatewayStartupPluginIds(...args), })); vi.mock("../plugins/registry.js", () => ({ @@ -113,7 +122,7 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => { applyPluginAutoEnable.mockClear(); initSubagentRegistry.mockClear(); loadGatewayStartupPlugins.mockClear(); - repairBundledRuntimeDepsInstallRootAsync.mockReset().mockResolvedValue({}); + repairBundledRuntimeDepsInstallRoot.mockReset().mockReturnValue({}); resolveBundledRuntimeDependencyPackageInstallRoot.mockClear(); resolveConfiguredDeferredChannelPluginIds.mockClear().mockReturnValue([]); resolveGatewayStartupPluginIds.mockClear().mockReturnValue(["memory-core"]); @@ -151,14 +160,14 @@ describe("prepareGatewayPluginBootstrap runtime-deps staging", () => { pluginIds: ["memory-core"], }), ); - expect(repairBundledRuntimeDepsInstallRootAsync).toHaveBeenCalledWith( + expect(repairBundledRuntimeDepsInstallRoot).toHaveBeenCalledWith( expect.objectContaining({ installRoot: "/runtime", missingSpecs: ["chokidar@^5.0.0"], installSpecs: expect.arrayContaining(["chokidar@^5.0.0", "typebox@^1.0.0"]), }), ); - expect(repairBundledRuntimeDepsInstallRootAsync.mock.invocationCallOrder[0]).toBeLessThan( + expect(repairBundledRuntimeDepsInstallRoot.mock.invocationCallOrder[0]).toBeLessThan( loadGatewayStartupPlugins.mock.invocationCallOrder[0] ?? Number.POSITIVE_INFINITY, ); });
src/gateway/server-startup-plugins.ts+78 −0 modified@@ -3,6 +3,12 @@ import { initSubagentRegistry } from "../agents/subagent-registry.js"; import { runChannelPluginStartupMaintenance } from "../channels/plugins/lifecycle-startup.js"; import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js"; +import { + repairBundledRuntimeDepsInstallRoot, + resolveBundledRuntimeDependencyPackageInstallRoot, + scanBundledPluginRuntimeDeps, +} from "../plugins/bundled-runtime-deps.js"; import { resolveConfiguredDeferredChannelPluginIds, resolveGatewayStartupPluginIds, @@ -21,6 +27,73 @@ type GatewayPluginBootstrapLog = { debug: (message: string) => void; }; +function prestageGatewayBundledRuntimeDeps(params: { + cfg: OpenClawConfig; + pluginIds: readonly string[]; + log: GatewayPluginBootstrapLog; +}): void { + if (params.pluginIds.length === 0) { + return; + } + const packageRoot = resolveOpenClawPackageRootSync({ + argv1: process.argv[1], + cwd: process.cwd(), + moduleUrl: import.meta.url, + }); + if (!packageRoot) { + return; + } + let scanResult: ReturnType<typeof scanBundledPluginRuntimeDeps>; + try { + scanResult = scanBundledPluginRuntimeDeps({ + packageRoot, + config: params.cfg, + pluginIds: [...params.pluginIds], + env: process.env, + }); + } catch (error) { + params.log.warn( + `[plugins] failed to scan bundled runtime deps before gateway startup; gateway startup will continue with per-plugin runtime-deps installs: ${String(error)}`, + ); + return; + } + const { deps, missing, conflicts } = scanResult; + if (conflicts.length > 0) { + params.log.warn( + `[plugins] bundled runtime deps have version conflicts: ${conflicts.map((conflict) => `${conflict.name} (${conflict.versions.join(", ")})`).join("; ")}`, + ); + } + if (missing.length === 0) { + return; + } + const missingSpecs = missing.map((dep) => `${dep.name}@${dep.version}`); + const installSpecs = deps.map((dep) => `${dep.name}@${dep.version}`); + const installRoot = resolveBundledRuntimeDependencyPackageInstallRoot(packageRoot, { + env: process.env, + }); + const startedAt = Date.now(); + params.log.info( + `[plugins] staging bundled runtime deps before gateway startup (${missingSpecs.length} missing, ${installSpecs.length} install specs): ${missingSpecs.join(", ")}`, + ); + try { + repairBundledRuntimeDepsInstallRoot({ + installRoot, + missingSpecs, + installSpecs, + env: process.env, + warn: (message) => params.log.warn(`[plugins] ${message}`), + }); + } catch (error) { + params.log.warn( + `[plugins] failed to stage bundled runtime deps before gateway startup after ${Date.now() - startedAt}ms; gateway startup will continue with per-plugin runtime-deps installs: ${String(error)}`, + ); + return; + } + params.log.info( + `[plugins] installed bundled runtime deps before gateway startup in ${Date.now() - startedAt}ms: ${missingSpecs.join(", ")}`, + ); +} + export async function prepareGatewayPluginBootstrap(params: { cfgAtStart: OpenClawConfig; startupRuntimeConfig: OpenClawConfig; @@ -89,6 +162,11 @@ export async function prepareGatewayPluginBootstrap(params: { let baseGatewayMethods = baseMethods; if (!params.minimalTestGateway) { + prestageGatewayBundledRuntimeDeps({ + cfg: gatewayPluginConfigAtStart, + pluginIds: startupPluginIds, + log: params.log, + }); ({ pluginRegistry, gatewayMethods: baseGatewayMethods } = loadGatewayStartupPlugins({ cfg: gatewayPluginConfigAtStart, activationSourceConfig: params.cfgAtStart,
Vulnerability mechanics
Root cause
"Policy bypass in embedded runner policy where requests using provider aliases are compared against aliases instead of canonical provider identities."
Attack vector
An attacker with local low-privileged access can exploit a policy bypass in the embedded runner policy. When the affected feature is enabled, requests using provider aliases are compared against aliases instead of canonical provider identities, allowing the attacker to select bundled tool access outside intended provider policy restrictions. The CVSS vector indicates local access, low complexity, and low privileges required, with a scope change allowing limited integrity impact.
Affected code
The vulnerability is in the embedded runner policy of OpenClaw before 2026.4.25. The patch modifies `src/gateway/server-startup-plugins.ts` to add a `prestageGatewayBundledRuntimeDeps` function that resolves and installs bundled plugin runtime dependencies before gateway startup, preventing policy bypass via provider alias confusion.
What the fix does
The patch introduces `prestageGatewayBundledRuntimeDeps` which scans and installs bundled runtime dependencies before gateway startup plugins are loaded. By staging these dependencies upfront, the fix ensures that provider aliases are resolved to canonical identities before policy enforcement occurs, preventing the alias confusion that allowed bypassing provider policy restrictions. The test changes also rename `repairBundledRuntimeDepsInstallRootAsync` to the synchronous `repairBundledRuntimeDepsInstallRoot`, aligning with the new synchronous staging approach.
Preconditions
- configThe affected embedded runner policy feature must be enabled in the OpenClaw configuration.
- authThe attacker must have local access to the system with low-privileged credentials.
- inputThe attacker must be able to make requests using provider aliases that differ from canonical provider identities.
Generated on Jun 11, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.