Feb 21, 2026·5 min read·6 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.