CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-JQPQ-MGVM-F9R6
7.80.12%

OpenClaw: The "Helpful" Path to Remote Code Execution

Alon Barad
Alon Barad
Software Engineer

Feb 18, 2026·7 min read·7 visits

PoC Available

Executive Summary (TL;DR)

OpenClaw versions prior to v2026.2.14 are vulnerable to Local Privilege Escalation and RCE via PATH manipulation. The software automatically trusted local `node_modules` binaries over system executables and allowed external agents to override the `PATH` environment variable during execution. Attackers can hijack commands to execute arbitrary code.

OpenClaw, an open-source platform for agent-based automation, suffered from a critical set of design flaws centered around how it handled the system `PATH` variable. By prioritizing local directories over system paths and allowing request-scoped environment overrides, the platform opened the door to classic binary hijacking and arbitrary code execution. This analysis covers the three distinct vectors: unsafe bootstrapping, relative command resolution, and environment injection.

The Hook: Convenience is the Enemy

We all love developer experience (DX). We love tools that "just work" without us having to fiddle with our shell configuration for three hours. But in the world of security, "it just works" usually translates to "it just executes whatever it finds." OpenClaw, a platform for building autonomous agents, fell into this classic trap.

At its core, OpenClaw is designed to orchestrate complex workflows. To do this, it often spawns its own CLI or other helper processes. The developers, likely tired of users complaining that the openclaw binary wasn't found, decided to write a helper function: ensureOpenClawCliOnPath. It sounds innocent enough. It's helpful. It's distinctively dangerous.

The vulnerability here isn't a buffer overflow or a complex heap grooming exercise. It's a logic flaw born from the desire to make things easy. The application prioritized untrusted, user-controlled directories over the system's trusted paths. It's the digital equivalent of a bank manager accepting a handwritten ID card because it was handed to them first.

The Flaw: Trusting the Locals

The root cause splits into a Hydra with three heads, all stemming from PATH mismanagement. The first head was the bootstrapper. When OpenClaw started, it wanted to ensure its binaries were accessible. Instead of checking if they were already there, it aggressively prepended the current working directory's node_modules/.bin to the PATH environment variable.

In Unix (and Windows) land, the PATH is processed in order. The first match wins. By putting a local, potentially user-controlled directory at the front of the line, OpenClaw effectively said: "I trust whatever is lying around in this folder more than I trust /usr/bin."

The second head was in the Agent Client Protocol (ACP). When the client needed to spawn a server instance, it didn't look for the absolute path of the binary it was currently running. It just shouted spawn("openclaw", ...) into the void. Node.js, being a compliant servant, looked at that compromised PATH, found the local impostor first, and executed it.

The third head was the node-host implementation. This component executes tasks requested by agents. It allowed these requests to pass in environment variable overrides. While it sanitized some things, it completely forgot that PATH is the master key to command resolution. An attacker could simply say, "Please run this task, but look for binaries in /tmp/evil first."

The Code: A Tale of Relative Paths

Let's look at the smoking gun in src/acp/client.ts. This is how the code was spawning the internal server before the fix:

// VULNERABLE CODE
import { spawn } from 'child_process';
 
export class AcpClient {
  startServer() {
    // Relies on system PATH, which might be poisoned
    // or prepended with local node_modules/.bin
    return spawn("openclaw", ["server", "--start"], {
      stdio: 'inherit'
    });
  }
}

And here is the patch from commit 013e8f6b3be3333a229a066eef26a45fec47ffcc. The developers realized that relying on a global name is suicide. They switched to resolving the absolute path of the entry point relative to the executing script.

// FIXED CODE
import { spawn } from 'child_process';
import { resolveSelfEntryPath } from '../utils/path';
 
export class AcpClient {
  startServer() {
    // Resolve absolute path to the binary
    const entryPath = resolveSelfEntryPath(import.meta.url);
    
    // Use the absolute path to the Node runtime
    return spawn(process.execPath, [entryPath, "server", "--start"], {
      stdio: 'inherit'
    });
  }
}

Meanwhile, in src/node-host/invoke.ts, the environment sanitization was virtually non-existent for PATH. The fix explicitly blocks sensitive keys. It's a simple if statement, but its absence was catastrophic.

// PATCH in src/node-host/invoke.ts
export function sanitizeEnv(overrides?: Record<string, string> | null) {
  const blockedEnvKeys = new Set(['LD_PRELOAD', 'PYTHONPATH', 'NODE_OPTIONS']);
  
  for (const [key, val] of Object.entries(overrides || {})) {
    const upper = key.toUpperCase();
    // THE FIX: Explicitly deny PATH manipulation
    if (upper === "PATH" || blockedEnvKeys.has(upper)) {
      continue;
    }
    // ... allow other env vars
  }
}

The Exploit: Shadowing the Binary

Exploiting this requires very little sophistication, which makes it all the more dangerous. Let's look at the Local Binary Hijacking vector. Imagine a developer pulls down a repo that includes a malicious postinstall script (or just a checked-in binary).

  1. Setup: The attacker places a malicious script named openclaw (or openclaw.cmd on Windows) inside node_modules/.bin/ or the project root.
  2. Trigger: The victim runs a legitimate OpenClaw command, perhaps to start an agent: openclaw run-agent.
  3. Execution: The CLI boots up. The vulnerable ensureOpenClawCliOnPath utility runs and prepends the current directory to PATH.
  4. The Pivot: The CLI internally decides it needs to spawn a child process for the ACP server. It calls spawn("openclaw").
  5. Payload: Instead of re-launching the legitimate binary, the OS resolves the attacker's script. Game over.

Here is a visualization of the flow:

The Network Vector is even cleaner. If you can communicate with the node-host (the component that runs agent tasks), you can send a task payload with environment overrides. sending {"env": {"PATH": "/tmp/attacker_controlled"}} forces the host to look for standard binaries (like ls, git, or docker) in your controlled directory first.

The Impact: Automation for the Attacker

Why does this matter? OpenClaw isn't a calculator app; it's an automation platform. It's designed to wield API keys, manage cloud infrastructure, and deploy code. It holds the keys to the kingdom.

If an attacker successfully hijacks the binary, they inherit the permissions of the automation agent. In a CI/CD context or a production server, this often means:

  1. Credential Theft: The attacker's script can simply read process.env to dump AWS keys, database connection strings, and Github tokens before passing execution back to the real binary to hide the tracks.
  2. Lateral Movement: Using the compromised host as a jump box to attack internal networks.
  3. Supply Chain Poisoning: If the developer is using OpenClaw to build software, the attacker can inject backdoors into the build artifacts silently.

This is a classic case of "High Impact, Low Complexity." It doesn't require a race condition or a specific memory layout. It just requires the software to function exactly as it was programmed.

The Fix: Absolute Absolutes

The fix provided in version v2026.2.14 is robust because it stops guessing.

First, they removed the automatic prepending of local directories. If you want to use a local binary, you now have to opt-in via OPENCLAW_ALLOW_PROJECT_LOCAL_BIN, and even then, it appends to the PATH rather than prepending. This ensures system binaries always take precedence.

Second, and most importantly, they switched to Absolute Path Resolution. By using process.execPath (the absolute path to the Node.js executable) and resolving the entry script's full path on disk, they bypassed the PATH lookup mechanism entirely for internal processes. You can't hijack a path if the application isn't looking for it.

For the node-host, they implemented a strict blocklist. PATH, PYTHONPATH, and LD_PRELOAD are now forbidden in request-scoped overrides.

> [!NOTE] > If you are a developer using OpenClaw, upgrade immediately. If you cannot upgrade, audit your environment to ensure no untrusted files exist in your node_modules or working directories before running the tool.

Official Patches

OpenClawCommit 013e8f6 fixing PATH handling

Fix Analysis (1)

Technical Appendix

CVSS Score
7.8/ 10
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
EPSS Probability
0.12%
Top 100% most exploited

Affected Systems

OpenClaw Agent HostsOpenClaw CLI environmentsCI/CD pipelines using OpenClaw

Affected Versions Detail

Product
Affected Versions
Fixed Version
openclaw/openclaw
OpenClaw
< 2026.2.142026.2.14
AttributeDetail
CWE IDCWE-427 / CWE-78
Attack VectorLocal & Network
CVSS Score7.8 (High)
Exploit MaturityProof-of-Concept
ImpactArbitrary Code Execution
Patch StatusFixed in v2026.2.14

MITRE ATT&CK Mapping

T1574.009Path Interception by Unquoted Path
Persistence
T1059Command and Scripting Interpreter
Execution
T1574Hijack Execution Flow
Privilege Escalation
CWE-427
Uncontrolled Search Path Element

Uncontrolled Search Path Element

Known Exploits & Detection

GitHub AdvisoryAdvisory containing PoC logic

Vulnerability Timeline

Vulnerability Reported by @akhmittra
2026-02-14
Fix Commit 013e8f6 Merged
2026-02-14
v2026.2.14 Released
2026-02-14
GHSA Advisory Published
2026-02-14

References & Sources

  • [1]GHSA-JQPQ-MGVM-F9R6

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.