Feb 21, 2026·5 min read·78 visits
OpenClaw's skill packager followed symlinks by default. If you packaged a folder with a link to /etc/passwd, you unknowingly mailed your password hashes to the internet. Fixed in 2026.2.18.
A classic UNIX nuance bites a modern AI tool. OpenClaw, the open-source personal AI assistant, contained a vulnerability in its skill packaging utility that allowed for arbitrary file read via symbolic link following. By crafting a malicious skill directory containing symlinks to sensitive files (like SSH keys or password databases), an attacker could trick a developer or user into packaging those external files into a distributable archive. Additionally, a secondary path traversal (Zip Slip) vulnerability was identified in the same component, allowing for potential file overwrites during extraction.
We all love personal AI assistants. They organize our calendars, tell us the weather, and—in the case of OpenClaw—accidentally bundle up our private SSH keys and ship them off to the internet. OpenClaw relies on a plugin system called "Skills." Users and developers create these skills in local directories and then use a helper script, package_skill.py, to zip them up for distribution. It sounds mundane, right? It's just a zipper.
But here is the thing about "mundane" utility scripts: they are often written with the assumption that the user is a benevolent operator working in a sterile environment. They rarely account for the fact that the directory being zipped might be a minefield laid by an attacker, or that the user might be accidentally pointing the gun at their own foot.
CVE-2026-27485 is a story about the difference between "files" and "paths," and what happens when Python's standard libraries do exactly what they are told to do, rather than what they should do. It turns a simple packaging utility into an unintentional data exfiltration tool.
The vulnerability lies in how UNIX filesystems handle symbolic links (symlinks) and how the Python zipfile library interacts with them. A symlink is essentially a signpost that says, "The data you are looking for is actually over there."
When you ask a standard archiving tool to zip up a directory, you have a choice: do you archive the signpost (the link itself), or do you follow the signpost and archive the destination?
By default, zipfile.ZipFile.write() is somewhat agnostic, but when combined with pathlib.Path.rglob("*") (which OpenClaw was using to find files), the script blindly grabbed every entry in the directory. When write() encounters a path, it resolves it. If that path is a symlink pointing to /home/user/.ssh/id_rsa, the script dutifully reads the private key and stuffs it into the archive under the filename id_rsa.
> [!NOTE] > This is not a memory corruption bug. It is a logic flaw. The code failed to ask the critical question: "Is this file actually inside the directory I am supposed to be packaging?"
To make matters worse, the developers also missed a secondary vulnerability: Zip Slip. The script accepted file paths without sanitizing them for directory traversal characters (../). This means not only could it suck data out (via symlinks), but a malicious archive could also write data over sensitive files on the victim's machine during extraction.
Let's look at the vulnerable code in skills/skill-creator/scripts/package_skill.py. It's a classic example of naive iteration.
# Before the fix
with zipfile.ZipFile(output_filename, 'w') as zipf:
# Recursively find all files
for file_path in skill_dir.rglob("*"):
# Calculate the relative path inside the zip
arcname = file_path.relative_to(skill_dir)
# WRITE IT ALL! No checks, no fear.
zipf.write(file_path, arcname)The fix, applied in commit c275932aa4230fb7a8212fe1b9d2a18424874b3f, introduces a sanity check. It explicitly asks if the file is a symlink and rejects it if so. It also checks for the Zip Slip traversal pattern.
# After the fix
for file_path in skill_dir.rglob("*"):
arcname = file_path.relative_to(skill_dir)
# 1. The Symlink Check
if file_path.is_symlink():
print(f"[ERROR] Symlinks are not allowed in skills: {file_path}")
return None
# 2. The Zip Slip Check
if ".." in arcname.parts or arcname.is_absolute():
print(f"[ERROR] Invalid path in skill: {arcname}")
return None
zipf.write(file_path, arcname)While this patches the immediate CVE, seasoned attackers might notice something interesting: is_symlink() only checks for symbolic links. It does not check for hard links. If an attacker can create a hard link on the same filesystem, this check is bypassed entirely.
Exploiting this requires social engineering or a supply chain attack context. We want to trick a developer into packaging a skill that contains a "trap."
assets/ folder of the skill, the attacker creates a symbolic link named config_backup.json that points to /etc/passwd or ~/.aws/credentials.
ln -s /home/victim/.aws/credentials my_skill/assets/defaults.jsonpackage_skill.py to build the skill for their own use.python package_skill.py my_skillassets/defaults.json.~/.aws/credentials.my_skill.zip.my_skill.zip to a public forum, Discord, or repository release page, effectively publishing their AWS credentials to the world.This is particularly dangerous in CI/CD environments. If a CI pipeline automatically packages skills found in a repo, a pull request adding a symlink could cause the CI runner to leak its own secrets into the build artifacts.
The primary mitigation is updating to OpenClaw version 2026.2.18 or 2026.2.19. These versions include the logic to reject symlinks and path traversal attempts.
As noted in the code analysis, the fix relies on file_path.is_symlink().
> [!WARNING] > Researcher Note: This patch is incomplete regarding Hard Links.
If the attacker has local access (or if the repo is checked out on a filesystem that supports hard links), they can use ln target link_name (without -s).
Hard links share the same inode as the original file. To Python's pathlib, a hard link looks exactly like a regular file. is_symlink() returns False. The zipfile library will happily read the content. However, hard links cannot cross filesystem boundaries (partitions), which limits the attack surface significantly compared to symlinks, which can point anywhere.
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
OpenClaw OpenClaw | <= 2026.2.17 | 2026.2.18 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-61 (Symlink Following) |
| Secondary CWE | CWE-22 (Path Traversal / Zip Slip) |
| CVSS v4.0 | 4.6 (Medium) |
| Attack Vector | Local / User Interaction Required |
| Impact | Information Disclosure (High Confidentiality Risk) |
| Patch Status | Fixed in 2026.2.18 |
The software does not properly resolve or reject symbolic links, allowing access to files outside the intended directory.
A logic error in Hatchet's OAuth state validation mechanism allows unauthenticated remote attackers to bypass state parameter verification. By submitting an empty state parameter, attackers can exploit an equality collision with cleared session keys, facilitating Login Cross-Site Request Forgery (Login CSRF) or Session Fixation.
A high-severity access control vulnerability in ToolHive CLI before v0.30.1 and ToolHive Studio before v0.38.0 allows local containerized MCP servers to bypass network isolation. This enables malicious workloads to establish TCP/IP connections to administrative and control plane endpoints exposed on the host loopback interface.
AnyCable is a real-time communication server. Prior to version 1.6.15, its Pusher-compatible REST API suffered from an authentication bypass vulnerability because it failed to verify that the request body matched the signature-validated body_md5 parameter. This allows attackers to perform replay attacks with modified body contents.
A denial-of-service vulnerability exists in AnyIO prior to version 4.14.2. Standard error streams of process-pool workers are connected to an operating system pipe that is never drained by the parent process. This allows a worker to fill the pipe buffer and deadlock indefinitely.
CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.
CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.