Feb 20, 2026·6 min read·35 visits
OpenClaw tried to stop AI agents from reading sensitive files by checking if files existed before running commands. Ironically, this check created a side-channel: if the system blocked the command, the attacker knew the file existed. If it didn't, the file was missing.
A logic flaw in the OpenClaw AI agent framework's command validation layer created a boolean side-channel, allowing attackers to probe the host filesystem. By attempting to prevent agents from accessing sensitive files via 'safe' binaries, the validation logic inadvertently revealed the existence of those files through error message discrepancies.
In the brave new world of Agentic AI, we want our digital minions to be capable but not too capable. We give them tools—specifically, shell access. But giving an LLM raw bash access is usually a résumé-generating event for security teams. So, frameworks like OpenClaw implement a concept called safeBins.
The idea is simple: allow the agent to run benign utilities like grep, sort, jq, and sed, but wrap them in a warm blanket of validation logic to ensure they aren't used to exfiltrate /etc/shadow or nuke the filesystem. It’s the 'sandbox' approach to shell execution.
However, in OpenClaw versions prior to 2026.2.19, this safety blanket had a hole in it. The mechanism designed to prevent the agent from touching sensitive files actually gave the agent a flashlight to find them. It turns out that when your security check involves asking the operating system, "Does this secret file exist?" and then acting differently based on the answer, you've just built a classic Oracle.
The vulnerability lies in src/infra/exec-approvals-allowlist.ts. The developers wanted to ensure that when an agent calls a binary like grep, it isn't pointing it at a sensitive file on the disk. To achieve this, they implemented a check that iterates through every token in the command's argument list (argv).
Here is where the logic went sideways. The validator used a helper function, essentially a wrapper around fs.existsSync, to check if an argument resolved to a real path on the host system. If the token resolved to an existing file, the validator would throw a specific error (allowlist miss) and block the execution.
Do you see the problem? It’s a boolean side-channel.
If I ask the agent to run grep pattern /etc/passwd and the system rejects it because "Wait, that's a real file!", I have confirmed the file exists. If I ask for grep pattern /etc/unicorn_tears and the system says "Command failed" (because grep couldn't find the file) or allows it to proceed, I know the file does not exist. The security mechanism itself became the very thing it was trying to prevent: a filesystem enumeration tool.
Let's look at the smoking gun. The vulnerable logic was relying on the filesystem state to make security decisions. This is a cardinal sin in secure coding—validation should be syntactic, not semantic regarding the environment state.
The Vulnerable Logic:
// Inside isSafeBinUsage loop
function defaultFileExists(filePath: string): boolean {
try {
return fs.existsSync(filePath);
} catch {
return false;
}
}
// ... later in the validation loop ...
if (exists(path.resolve(cwd, token))) {
// THE LEAK: The decision to return false (deny) depends on the disk state.
return false;
}The Fix (Commit bafdbb6f112409a65decd3d4e7350fbd637c7754):
The patch, authored by Peter Steinberger, rips out the fs.existsSync dependency entirely. Instead of asking the disk, it parses the command arguments deterministically. It introduces SAFE_BIN_PROFILES—strict definitions for each binary that dictate exactly which flags are allowed (e.g., blocking -f in grep or -o in sort) and how many positional arguments can be passed.
// The new approach: Deterministic parsing
const profile = SAFE_BIN_PROFILES[binaryName];
// Blocked flags are rejected regardless of what follows them
if (profile.blockedFlags.has(arg)) {
return false;
}
// Positional arguments are counted, not checked against the disk
if (!arg.startsWith("-")) {
positionalCount++;
if (positionalCount > profile.maxPositional) {
return false;
}
}By moving to a syntax-based allowlist, the response is now constant regardless of whether /secret/config.json actually exists.
Exploiting this is trivially easy for anyone controlling the agent's input or instructions. We don't need memory corruption; we just need to observe the error messages. Let's assume we want to map out the server's directory structure.
Step 1: Calibration First, we establish our baseline responses.
Probe: sort /etc/passwd (We know this exists on Linux)
Response: Error: Execution denied: allowlist miss (The Oracle confirms existence)
Probe: sort /etc/does_not_exist_12345
Response: Error: Command failed (or a generic execution error because sort ran and failed)
Step 2: The Attack Now we script the agent to brute-force interesting paths.
const targets = [
"/root/.ssh/id_rsa",
"/home/user/.aws/credentials",
"/var/run/docker.sock",
"/proc/1/environ"
];
for (const target of targets) {
const result = await agent.exec(`grep -l foo ${target}`);
if (result.error.includes("allowlist miss")) {
console.log(`[+] FOUND: ${target}`);
} else {
console.log(`[-] MISS: ${target}`);
}
}This allows an attacker to perform reconnaissance without ever successfully executing a command against a file. It's akin to a blind SQL injection, but for the filesystem.
You might look at the CVSS score of 4.3 and think, "Meh, it's just information disclosure." And sure, on its own, knowing a file exists isn't the same as reading it. But in the context of an attack chain, reconnaissance is king.
Knowing exactly where the config.json lives, or confirming the presence of a specific Docker socket, or determining which version of a library is installed by probing for version-specific files, allows an attacker to tailor their next stage payload with precision.
Furthermore, this vulnerability exposes the presence of security tools, logs, or other artifacts that an attacker might want to avoid or target. In a containerized environment, mapping the filesystem is often step one in a container breakout. The oracle provided by OpenClaw was a free map.
The mitigation is straightforward: Update OpenClaw to version 2026.2.19 or later. The fix changes the validation philosophy from "Check the world" to "Check the syntax."
If you are building your own command execution validators, take this as a lesson: Never use environmental state (file existence, network reachability, user presence) as a condition for input validation if the failure state is visible to the user. It will always leak information.
Instead, use strict allowlists for arguments. If grep supports a -f flag that reads a file, don't check if the file exists—just ban the -f flag entirely if you don't want files being read. Syntax is predictable; the filesystem is not.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
openclaw OpenClaw | <= 2026.2.17 | 2026.2.19 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-203 |
| Attack Vector | Network (Agent Session) |
| CVSS | 4.3 (Medium) |
| Risk | Filesystem Enumeration |
| Impact | Information Disclosure |
| Exploit Status | PoC Available |
Observable Discrepancy
An uncontrolled resource consumption vulnerability exists in the Scala-based http4s-blaze-server package of the http4s/blaze library. The vulnerability allows remote, unauthenticated attackers to cause an Out of Memory Error (OOM) and JVM crash by streaming a continuous sequence of small or empty WebSocket continuation frames with the FIN bit set to 0. This bypasses typical payload size checks because of the JVM's per-object allocation overhead, leading to rapid heap exhaustion with minimal network bandwidth.
A critical path traversal vulnerability has been identified in the OpenList Go-based backend package. The vulnerability exists within the batch rename handler because the application does not validate the source filename parameter before constructing filesystems paths. This omission allows authenticated users to escape their designated directory and rename files in sibling paths.
OpenList version 4.2.3 and prior is vulnerable to an authorization bypass and metadata leakage. When configured with the Bleve search engine backend, OpenList fails to perform separator-aware path matching when validating tenant containment. This allows authenticated users to access sibling directories sharing similar name prefixes. Furthermore, the search backend returns unfiltered global result counts, leaking existence verification data of unauthorized files via side-channel analysis.
An authorization bypass vulnerability in OpenList version 4.2.3 and below allows authenticated users to read arbitrary files outside of their designated base directories due to an insecure path prefix check using Go's standard strings.HasPrefix function.
A security policy bypass vulnerability exists in the AWS API MCP Server (awslabs-aws-api-mcp-server) from version 0.2.13 through 1.3.46. When the server fails to load the read-only operations index during startup (due to transient network failures, file permission issues, or other exceptions), it logs a warning but continues running in an insecure, degraded state. Under this condition, the security policy engine fails open, silently skipping all subsequent security checks and consent prompts for the lifetime of the process. This permits unauthorized mutating AWS CLI commands to execute via indirect prompt injection attacks.
An incomplete escaping vulnerability in the npm package 'shescape' allows unauthenticated users to trigger dynamic shell expansions, absolute path disclosure, and command block break-outs on Unix and Windows systems.