Feb 17, 2026·7 min read·8 visits
Critical Path Traversal in OpenClaw's plugin installer allows attackers to write files to arbitrary locations via malicious package names. Fixed in v2026.2.1.
In the race to build the ultimate AI assistant, the developers of OpenClaw (formerly Moltbot) forgot one of the oldest rules in the book: never trust user input, especially when that input tells you where to write files. GHSA-QRQ5-WJGG-RVQW is a critical Path Traversal vulnerability that turns a helpful plugin installation process into an arbitrary file write primitive. By simply crafting a malicious 'package.json', an attacker can escape the application's sandbox and overwrite system files, leading to Remote Code Execution (RCE). It's a classic case of convenience over security, proving that even modern AI frameworks can suffer from 1990s-era bugs.
OpenClaw (previously known as Clawdbot and Moltbot) pitches itself as the ultimate open-source AI personal assistant framework. It's the kind of tool developers love: modular, extensible, and seemingly intelligent. It allows users to install "skills" (plugins) and "hooks" to expand its capabilities. Want your bot to check the weather? Install a weather plugin. Want it to manage your calendar? There's a hook for that.
But here's the catch: the mechanism that installs these plugins is trusting to a fault. When you install a plugin, the system has to decide where to put the files on your disk. A secure system would say, "I will put this in the plugins/ directory, labeled with a random ID or a sanitized name." OpenClaw, however, took a different approach. It essentially asked the plugin, "Hey, what's your name and where would you like to stay?"
This vulnerability isn't some complex heap overflow or a subtle race condition. It is a logic flaw in how the application processes file paths from the plugin's manifest (package.json). Because the application failed to realize that a name could contain directory traversal characters like .., it allowed malicious plugins to break out of their designated sandbox. It's the digital equivalent of inviting a guest into your home and telling them, "Go sleep in the room labeled 'Master Bedroom'," but they've labeled their suitcase "../Neighbor's House/Living Room."
The root cause of this vulnerability lies in the unscopedPackageName function and the subsequent file path construction. When a user runs a command like openclaw plugins install, the CLI reads the package.json of the target package to determine its identity. The logic was intended to strip npm scopes (e.g., changing @my-scope/my-plugin to my-plugin) to keep directory names clean.
However, the developers missed a critical edge case: what if the "name" isn't just a name? What if it's a path?
The vulnerable code essentially did this:
name from package.json.path.join(extensionsDir, theName).In Node.js, path.join is purely mechanical. It doesn't check if the resulting path is safe; it just concatenates strings using the OS separator. If the plugin name is something innocuous like weather-bot, the path becomes /app/extensions/weather-bot. But if an attacker sets the name to @evil/../../../../etc/cron.d, the scope stripper might return ../../../../etc/cron.d. When joined with /app/extensions/, the result resolves to /etc/cron.d. The application then proceeds to extract the plugin's contents into that directory.
This is a textbook Path Traversal (CWE-22). The application blindly trusted that the identifier provided by the package was a safe filename, not a relative path instruction.
Let's look at the difference between the naive implementation and the hardened code. The vulnerability existed in src/hooks/install.ts and src/plugins/install.ts.
The Vulnerable Logic (Conceptual):
// The code implicitly trusted the output of unscopedPackageName
const pluginName = getUnscopedName(packageJson.name);
const installDir = path.join(EXTENSIONS_DIR, pluginName);
// ... extract files to installDir ...If pluginName is ../../tmp, installDir becomes /tmp (depending on the base path). The installer creates the directory and dumps files there.
The Fix (Commit d03eca8450dc493b198a88b105fd180895238e57):
The fix introduced in v2026.2.1 is robust. It employs two strategies: sanitization and verification.
path.relative to ensure the final path is actually inside the target directory.function resolveSafeInstallDir(
extensionsDir: string,
pluginId: string,
): { ok: true; path: string } | { ok: false; error: string } {
// 1. Sanitize: Replace / and \ with __
const safeId = safeDirName(pluginId);
// 2. Resolve absolute paths
const targetDir = path.join(extensionsDir, safeId);
const resolvedBase = path.resolve(extensionsDir);
const resolvedTarget = path.resolve(targetDir);
// 3. Verify hierarchy using path.relative
const relative = path.relative(resolvedBase, resolvedTarget);
if (
!relative ||
relative === ".." ||
relative.startsWith(`..${path.sep}`) ||
path.isAbsolute(relative)
) {
return { ok: false, error: "invalid plugin name: path traversal detected" };
}
return { ok: true, path: targetDir };
}> [!NOTE]
> The use of path.relative here is the "gold standard" check. It calculates the relationship between the base and the target. If the relationship starts with .., it means the target has escaped the base. Simple, elegant, and secure.
To exploit this, we don't need buffer overflows or heap spraying. We just need a text editor and a malicious intention. The goal is to create a plugin that, when installed, writes a file to a sensitive location (like /etc/cron.d for persistence or ~/.ssh for access).
Step 1: Create the Malicious Manifest
Create a directory for your "plugin" and add a package.json. The trick is in the name field.
{
"name": "@innocuous/../../../../tmp/pwned",
"version": "1.0.0",
"description": "Just a harmless weather plugin...",
"main": "index.js"
}Step 2: Add the Payload
In the same directory, create the file you want to drop. If we want to drop a file named hack.sh into /tmp/pwned/, we just include it in the package structure.
Step 3: Pack and Install
Normally, plugins are installed from npm or a tarball. If we point the OpenClaw CLI to our local directory or a hosted tarball:
openclaw plugins install ./my-evil-pluginThe CLI reads the manifest, sees the name, calculates the path [base_dir]/../../../../tmp/pwned, and happily extracts our contents there.
The Result
If the application is running as root (which, terrifically, many Dockerized bots do), you just overwrote arbitrary files on the filesystem. If you overwrite a startup script or a cron job, you have achieved Remote Code Execution (RCE).
The impact of arbitrary file write cannot be overstated. In the context of an AI assistant, this is particularly dangerous because these bots often have access to sensitive data (API keys, calendars, emails) and run in environments with significant network access.
/etc/cron.d), systemd unit paths, or user profile scripts (.bashrc), an attacker gains full code execution on the next trigger.This isn't just about breaking the bot; it's about using the bot as a pivot point into the internal network. If the bot is running inside a corporate network, that file write is your beachhead.
If you are running OpenClaw (or Moltbot/Clawdbot), you need to update immediately. The vulnerability was patched in version 2026.2.1.
For Developers: This serves as a critical lesson in input validation. Never trust data just because it comes from a "manifest" file. Files inside a tarball or fields in a JSON file are just as untrusted as an HTTP request body.
../) alone; it is often bypassable.path.relative.For Ops/Admins:
extensions/ and hooks/ directories.root turns a file write bug into a system wipe bug. A bot running as nobody limits the blast radius significantly.CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
OpenClaw OpenClaw | < 2026.2.1 | 2026.2.1 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Path Traversal |
| Attack Vector | Network (via Package Install) |
| CWE ID | CWE-22 |
| CVSS Score | 8.8 (High) |
| Impact | Arbitrary File Write / RCE |
| Fix Version | v2026.2.1 |
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')