Jul 29, 2026·5 min read·14 visits
A UNIX symbolic link following vulnerability in skilo's installation routine allows attackers to exfiltrate arbitrary local files readable by the target user.
A local file disclosure vulnerability exists in the Rust-based package skilo. When copying files during skill installation, the application recursively traverses directories but dereferences symbolic links, resulting in unauthorized local file reading.
The Rust-based package skilo is designed to install custom skills from remote and local sources using the skilo add command. This feature clones or copies files from a source directory into the user's local installation directory. During the installation process, the application recursively walks the source folder structure and copies individual files to their target destinations.\n\nThe copy operations are handled by a recursive helper function that fails to validate the nature of files before processing. Specifically, the system does not safely handle UNIX symbolic links (symlinks) encountered within the source repository. When a symlink is encountered, the tool retrieves the file contents of the resolved target path rather than preserving or rejecting the link itself.\n\nThis behavior introduces a local file disclosure vulnerability. An attacker can host a crafted skill directory containing a symbolic link pointing to a sensitive file on the victim's host filesystem, such as private keys or local configuration files. When the victim executes the installation command, the tool copies the contents of the target file into the newly installed skill directory, allowing subsequent unauthorized access.
The core flaw lies in a mismatch between how file metadata is queried and how the file is copied during recursive traversal. In src/commands/add.rs, the recursive helper function copy_dir_all reads directory entries using std::fs::read_dir(src). For each entry, it queries the file type using std::fs::DirEntry::file_type().\n\nIn the Rust standard library, DirEntry::file_type() retrieves metadata directly from the directory entry itself without following symbolic links. Consequently, if an entry is a symbolic link, ty.is_dir() returns false. This causes execution to fall through to the else block, which assumes the entry is a regular file.\n\nThe application then attempts to copy the entry using std::fs::copy(&src_path, &dst_path). Unlike DirEntry::file_type(), std::fs::copy() traverses and dereferences symbolic links. It reads the contents of the file pointed to by the symbolic link and writes them to a new, regular file at the destination path. This creates a regular file containing the sensitive target data inside the installation directory.
rust\n// Vulnerable Implementation (v0.5.0 - v0.11.0)\nfn copy_dir_all(src: &Path, dst: &Path) -> Result<(), SkiloError> {\n fs::create_dir_all(dst)?;&\n for entry in fs::read_dir(src)? {\n let entry = entry?;\n let ty = entry.file_type()?; // Queries metadata of the entry itself\n let src_path = entry.path();\n let dst_path = dst.join(entry.file_name());\n\n if ty.is_dir() {\n copy_dir_all(&src_path, &dst_path)?;\n } else {\n // If entry is a symlink, ty.is_dir() is false.\n // fs::copy dereferences the symlink and reads the external target file.\n fs::copy(&src_path, &dst_path)?;\n }\n }\n Ok(())\n}\n\n\nThe patch implemented in version 0.11.1 resolves this flaw by explicitly invoking ty.is_symlink(). If a symbolic link is detected, the function immediately terminates execution and returns a custom error, preventing the file from being copied.\n\nrust\n// Patched Implementation (v0.11.1)\nfn copy_dir_all(src: &Path, dst: &Path) -> Result<(), SkiloError> {\n fs::create_dir_all(dst)?;&\n for entry in fs::read_dir(src)? {\n let entry = entry?;\n let ty = entry.file_type()?;\n let src_path = entry.path();\n let dst_path = dst.join(entry.file_name());\n\n if ty.is_symlink() {\n // Refuse symlinks explicitly to prevent dereference leakage\n return Err(SkiloError::SymlinkInSkill {\n path: src_path.display().to_string(),\n });\n } else if ty.is_dir() {\n copy_dir_all(&src_path, &dst_path)?;\n } else {\n fs::copy(&src_path, &dst_path)?;\n }\n }\n Ok(())\n}\n
An attacker can exploit this vulnerability by publishing a malicious skill containing a symbolic link. The link is crafted to point to a standard file path containing sensitive data, such as /home/user/.ssh/id_rsa or /home/user/.aws/credentials.\n\nbash\n# Attacker directory setup\nmkdir malicious-skill\ncd malicious-skill\n# Create symlink pointing to user's SSH private key\nln -s /home/user/.ssh/id_rsa target_data.txt\n\n\nThe attacker hosts the repository on a public platform and induces the target user to run the installation command. When the user executes the command, the application retrieves the remote repository and executes the file copy routine.\n\nbash\nskilo add github.com/attacker/malicious-skill\n\n\nDuring the copy phase, the copy_dir_all function processes target_data.txt. Because the link is dereferenced during the copy operation, the victim's private key contents are copied into ~/.local/share/skilo/skills/malicious-skill/target_data.txt. The attacker can retrieve these contents if the application synchronizes skill directories to external backends or exposes skill files to the agent workspace.
The vulnerability has a CVSS v3.1 score of 6.5, reflecting medium severity. The impact is restricted to local file disclosure. The attacker cannot modify target files or execute arbitrary commands directly through this vector, resulting in zero integrity and availability impact.\n\nThe confidentiality impact is high because the vulnerability allows the exfiltration of any local files readable by the user executing the skilo command. This includes SSH private keys, cloud configuration tokens, database credentials, and system environment variables.\n\nThe attack vector is classified as network because the exploit payload is delivered via a remote Git repository. The user must be tricked into installing the malicious skill, which requires active user interaction. The attack complexity is low as the exploit relies on standard UNIX filesystem mechanisms.
Users must upgrade skilo to version 0.11.1 or higher to eliminate the vulnerability. This patch introduces validation that rejects any installation source containing symbolic links. The application fails closed and refuses to copy files if a symlink is encountered.\n\nIf immediate upgrade is not possible, users must inspect remote repositories before executing the installation command. The presence of symbolic links in a directory can be verified by executing the find command on the cloned repository.\n\nbash\nfind /path/to/skill/repository -type l\n\n\nAdditionally, executing the application under a dedicated, low-privileged user account limits the scope of accessible files. Isolating the installation environment using containers or sandbox utilities prevents access to sensitive host directories like ~/.ssh or ~/.aws.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
skilo manuelmauro | >= 0.5.0, < 0.11.1 | 0.11.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-59 |
| Attack Vector | Network |
| CVSS Score | 6.5 |
| EPSS Score | 0.00045 |
| Impact | Confidentiality (High) |
| Exploit Status | poc |
| KEV Status | Not Listed |
Improper Link Resolution Before File Access ('Link Following')
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.