VYPR
High severityNVD Advisory· Published Feb 19, 2026· Updated Feb 20, 2026

OpenClaw Gateway tool allowed unrestricted gatewayUrl override

CVE-2026-26322

Description

OpenClaw is a personal AI assistant. Prior to OpenClaw version 2026.2.14, the Gateway tool accepted a tool-supplied gatewayUrl without sufficient restrictions, which could cause the OpenClaw host to attempt outbound WebSocket connections to user-specified targets. This requires the ability to invoke tools that accept gatewayUrl overrides (directly or indirectly). In typical setups this is limited to authenticated operators, trusted automation, or environments where tool calls are exposed to non-operators. In other words, this is not a drive-by issue for arbitrary internet users unless a deployment explicitly allows untrusted users to trigger these tool calls. Some tool call paths allowed gatewayUrl overrides to flow into the Gateway WebSocket client without validation or allowlisting. This meant the host could be instructed to attempt connections to non-gateway endpoints (for example, localhost services, private network addresses, or cloud metadata IPs). In the common case, this results in an outbound connection attempt from the OpenClaw host (and corresponding errors/timeouts). In environments where the tool caller can observe the results, this can also be used for limited network reachability probing. If the target speaks WebSocket and is reachable, further interaction may be possible. Starting in version 2026.2.14, tool-supplied gatewayUrl overrides are restricted to loopback (on the configured gateway port) or the configured gateway.remote.url. Disallowed protocols, credentials, query/hash, and non-root paths are rejected.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openclawnpm
< 2026.2.142026.2.14

Affected products

1

Patches

1
c5406e1d2434

fix(security): prevent gatewayUrl SSRF

https://github.com/openclaw/openclawPeter SteinbergerFeb 14, 2026via ghsa
4 files changed · +61 2
  • CHANGELOG.md+1 0 modified
    @@ -25,6 +25,7 @@ Docs: https://docs.openclaw.ai
     - Security/Exec approvals: prevent safeBins allowlist bypass via shell expansion (host exec allowlist mode only; not enabled by default). Thanks @christos-eth.
     - Security/Gateway: block `system.execApprovals.*` via `node.invoke` (use `exec.approvals.node.*` instead). Thanks @christos-eth.
     - Security/Exec: harden PATH handling by disabling project-local `node_modules/.bin` bootstrapping by default, disallowing node-host `PATH` overrides, and spawning ACP servers via the current executable by default. Thanks @akhmittra.
    +- Security/Gateway: prevent SSRF by ignoring user-provided `gatewayUrl` tool inputs (gateway URL must come from config). Thanks @p80n-sec.
     - CLI: fix lazy core command registration so top-level maintenance commands (`doctor`, `dashboard`, `reset`, `uninstall`) resolve correctly instead of exposing a non-functional `maintenance` placeholder command.
     - Telegram: when `channels.telegram.commands.native` is `false`, exclude plugin commands from `setMyCommands` menu registration while keeping plugin slash handlers callable. (#15132) Thanks @Glucksberg.
     - Security/Agents: scope CLI process cleanup to owned child PIDs to avoid killing unrelated processes on shared hosts. Thanks @aether-ai-agent.
    
  • src/agents/tools/gateway.e2e.test.ts+1 1 modified
    @@ -20,7 +20,7 @@ describe("gateway tool defaults", () => {
         expect(opts.url).toBeUndefined();
       });
     
    -  it("passes through explicit overrides", async () => {
    +  it("accepts allowlisted gatewayUrl overrides (SSRF hardening)", async () => {
         callGatewayMock.mockResolvedValueOnce({ ok: true });
         await callGatewayTool(
           "health",
    
  • src/infra/outbound/message.e2e.test.ts+51 0 modified
    @@ -3,6 +3,7 @@ import type { ChannelOutboundAdapter, ChannelPlugin } from "../../channels/plugi
     import { setActivePluginRegistry } from "../../plugins/runtime.js";
     import { createTestRegistry } from "../../test-utils/channel-plugins.js";
     import { createIMessageTestPlugin } from "../../test-utils/imessage-test-plugin.js";
    +import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
     import { sendMessage, sendPoll } from "./message.js";
     
     const setRegistry = (registry: ReturnType<typeof createTestRegistry>) => {
    @@ -172,6 +173,56 @@ describe("sendPoll channel normalization", () => {
       });
     });
     
    +describe("gateway url override hardening", () => {
    +  beforeEach(() => {
    +    callGatewayMock.mockReset();
    +    setRegistry(emptyRegistry);
    +  });
    +
    +  afterEach(() => {
    +    setRegistry(emptyRegistry);
    +  });
    +
    +  it("drops gateway url overrides in backend mode (SSRF hardening)", async () => {
    +    setRegistry(
    +      createTestRegistry([
    +        {
    +          pluginId: "mattermost",
    +          source: "test",
    +          plugin: {
    +            ...createMattermostLikePlugin({ onSendText: () => {} }),
    +            outbound: { deliveryMode: "gateway" },
    +          },
    +        },
    +      ]),
    +    );
    +
    +    callGatewayMock.mockResolvedValueOnce({ messageId: "m1" });
    +    await sendMessage({
    +      cfg: {},
    +      to: "channel:town-square",
    +      content: "hi",
    +      channel: "mattermost",
    +      gateway: {
    +        url: "ws://169.254.169.254:80/latest/meta-data/",
    +        token: "t",
    +        timeoutMs: 5000,
    +        clientName: GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
    +        clientDisplayName: "agent",
    +        mode: GATEWAY_CLIENT_MODES.BACKEND,
    +      },
    +    });
    +
    +    expect(callGatewayMock).toHaveBeenCalledWith(
    +      expect.objectContaining({
    +        url: undefined,
    +        token: "t",
    +        timeoutMs: 5000,
    +      }),
    +    );
    +  });
    +});
    +
     const emptyRegistry = createTestRegistry([]);
     
     const createMSTeamsOutbound = (opts?: { includePoll?: boolean }): ChannelOutboundAdapter => ({
    
  • src/infra/outbound/message.ts+8 1 modified
    @@ -102,8 +102,15 @@ export type MessagePollResult = {
     };
     
     function resolveGatewayOptions(opts?: MessageGatewayOptions) {
    +  // Security: backend callers (tools/agents) must not accept user-controlled gateway URLs.
    +  // Use config-derived gateway target only.
    +  const url =
    +    opts?.mode === GATEWAY_CLIENT_MODES.BACKEND ||
    +    opts?.clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT
    +      ? undefined
    +      : opts?.url;
       return {
    -    url: opts?.url,
    +    url,
         token: opts?.token,
         timeoutMs:
           typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs)
    

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.