Jun 23, 2026·6 min read·15 visits
Multiple critical vulnerabilities in skillctl allow malicious repositories to perform argument injection, system-wide directory deletion, persistent denial of service, commit forgery, and credential exfiltration when processed by AI developer agents.
An in-depth security audit of the skillctl command-line package manager revealed five critical and high-severity security vulnerabilities. The identified flaws span parameter-level command argument injection via the source_sha parameter, uncontrolled resource consumption (Denial of Service) through unnamed UNIX FIFOs and character devices, directory path traversal in the destination argument, commit-message trailer forgery via newline injection in skill names, and local credential exfiltration leveraging UNIX hardlinks. These vulnerabilities represent significant vectors for workstation compromise when executing agentic tasks in repositories containing untrusted files or pull requests. Remediation was introduced in version v0.1.3.
The command-line package manager skillctl coordinates, installs, and updates 'skills' across multiple AI-assisted coding environments, including Claude Code, Cursor, and Codex. In these automated spaces, LLM agents translate natural language instructions or untrusted context files into local shell configurations and software dependencies. Because an agent executes commands based on the contents of open repositories, any input boundaries processed by skillctl represent a high-priority attack surface.
A technical security review of the tool identified five severe design flaws: parameter-level command argument injection, resource exhaustion leading to denial of service, directory path traversal, metadata injection, and hardlink file replication. These vulnerabilities bypass standard path protections and validation routines to target the local operator's workspace environment.
The first flaw lies within InstalledSkill.source_sha, which is parsed from the localized configuration file .skills.toml. Because this file resides inside a project workspace, an attacker can modify its keys via a standard commit or pull request. The application passes the parameter directly to a subprocess running git ls-tree -r -z <refspec> -- <path>. By setting the refspec to a flag (such as --name-only), an attacker corrupts the downstream diff execution path, forcing local destructive actions.
The second vulnerability resides in the fs_util::copy_dir_all recursive copy utility. When copying directories, the tool historically validated directory entries with is_dir() and is_symlink(). Any other file descriptor, such as a FIFO named pipe, a character device (/dev/zero), or a block device, was passed directly to std::fs::copy. A FIFO blocks the execution routine indefinitely waiting for a writer, while a character device streams infinite bytes, resulting in disk space exhaustion or an Out-Of-Memory (OOM) error.
The third vulnerability is a path traversal in the --dest argument of the add subcommand. Because the CLI accepted relative parent indicators (..) and absolute paths without sanitization, an LLM agent instructed by adversarial prompt injections could be manipulated into launching a recursive deletion command against arbitrary directories, such as ~/.ssh, using the --on-conflict overwrite flag.
The fourth vulnerability, CRLF metadata injection, stems from writing unescaped skill names directly to git commit arguments (e.g., git commit -m "update skill: <name>"). Including newlines inside a skill's name allows an attacker to forge commit-message trailers (such as Co-Authored-By: ...), which automated release tools and CI/CD pipelines use for authentication and logging. Finally, the fifth vulnerability involves hardlinks. While symbolic links were historically restricted, hardlinks share the target file's inode and are evaluated as normal files by std::fs::symlink_metadata. A hardlinked credential file placed in a skill folder would be fully copied into the public workspace directory by the application and subsequently exfiltrated via skillctl push.
To analyze the remediation, we inspect the changes in commit 28dfce3865c10bf2072081ee621d1adbcc2f19e1. The maintainer hardened the parameter resolution and introduced strict path checks.
In src/commands/add.rs, absolute path validation was restricted when running in agent or non-interactive environments, and directory traversal characters are now rejected unconditionally:
for component in dest.components() {
if matches!(component, std::path::Component::ParentDir) {
return Err(AppError::Config(format!(
"invalid --dest `{}`: parent traversal (`..`) is not allowed",
dest.display()
))
.into());
}
}
if dest.is_absolute() && !ctx.interactive {
return Err(AppError::Config(format!(
"invalid --dest `{}`: absolute paths are not allowed in non-interactive mode",
dest.display()
))
.into());
}In src/fs_util.rs, the copier was updated to enforce strict file type assertions and unix link-count checks (nlink() > 1) to block both hardlink resolution and special device file-type handling:
if file_type.is_dir() {
copy_dir_all(&from, &to)?;
} else if file_type.is_file() {
reject_hardlink(&from)?;
fs::copy(&from, &to)?;
} else {
return Err(AppError::Config(format!(
"refusing to copy `{}`: not a regular file or directory (FIFO, socket, device, or other special file)",
from.display()
))
.into());
}Under UNIX platforms, the hardlink validation routine inspects file metadata to count active inodes:
#[cfg(unix)]
fn reject_hardlink(path: &Path) -> Result<()> {
use std::os::unix::fs::MetadataExt;
let meta = fs::metadata(path)?;
if meta.nlink() > 1 {
return Err(AppError::Config(format!(
"refusing to copy `{}`: file has {} hardlinks",
path.display(),
meta.nlink()
))
.into());
}
Ok(())
}Finally, in src/project_config.rs, the source_sha parameter is explicitly restricted to a strict hexadecimal length-checked regular set to prevent parameter injection:
if !is_hex_sha(&self.source_sha) {
return Err(AppError::Config(format!(
"invalid source_sha: expected 40-64 hex characters, got `{}`",
self.source_sha
)));
}Exploitation of these flaws requires an attacker to control a workspace processed by skillctl. For example, in an argument injection attack, a user reviews or checks out an untrusted pull request containing a malicious .skills.toml file. When the agent synchronizes the project state, the argument is integrated into the downstream git ls-tree CLI execution, manipulating its runtime flags.
For hardlink-based credential exfiltration, the attacker commits a hardlinked file pointing to a highly sensitive file path, such as ~/.ssh/id_rsa. When the victim clones the repository and runs skillctl add, the copy routine resolving the duplicate inode writes the private key file directly into the local repository folder. The file is subsequently committed and pushed back to the attacker's server during synchronization.
To prevent regressions, the developer introduced a comprehensive suite of automated integration tests verifying each failure path:
#[test]
fn copy_dir_all_rejects_fifo_inside_source() {
let src = TempDir::new().unwrap();
let dst = TempDir::new().unwrap();
let fifo_path = src.path().join("evil_pipe");
std::process::Command::new("mkfifo")
.arg(&fifo_path)
.status()
.unwrap();
let result = copy_dir_all(src.path(), &dst.path().join("out"));
assert!(result.is_err());
}These vulnerabilities expose developer environments to significant risks. Since skillctl runs locally within the developer's shell session and with user-level privileges, successful exploitation allows an attacker to manipulate Git configurations, delete system configurations, and exfiltrate private authentication keys. Because NVD has not assigned a CVE ID, these risks may remain undetected by standard software composition analysis (SCA) scanners, which typically rely on official NVD feeds rather than GHSA advisories.
Although the flaws are primarily triggered locally, the vulnerability context is highly amplified because the target application is specifically integrated into LLM-driven autonomous workflows. Autonomous agents frequently lack the context or guardrails necessary to identify subtle path manipulations or command injection attempts in untrusted files, converting passive file reading into active system compromises.
A technical review of the remediation commit reveals two security limitations. First, the hardlink protection mechanism is gated behind a UNIX compilation flag (#[cfg(unix)]). On Windows compilation systems, the application falls back to an empty stub function:
#[cfg(not(unix))]
fn reject_hardlink(_path: &Path) -> Result<()> { Ok(()) }Because Windows NTFS natively supports hardlinks, a developer running the application on Windows remains vulnerable to local file exfiltration using the exact same hardlink copying attack vector.
Second, the implementation introduces a Time-of-Check to Time-of-Use (TOCTOU) race condition. There is a temporal gap between verifying file metadata via reject_hardlink or symlink_metadata and executing the subsequent copy operation with fs::copy. A highly synchronized local exploit process could swap a standard file with a hardlink, pipe, or symbolic link inside this execution window, bypassing the runtime security checks.
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
skillctl umanio-agency | < v0.1.3 | v0.1.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-88, CWE-400, CWE-22, CWE-93, CWE-59 |
| Attack Vector | Local / Social Engineering via Malicious Repository Configuration |
| CVSS v3.1 | 8.6 (High) |
| Exploit Status | Proof of Concept (PoC) available in official test suite |
| Impact | Command Execution, Directory Deletion, Denial of Service, Local Secret Exfiltration |
| Remediation Status | Patched in version v0.1.3 |
The software constructs a command string or command array using external input but does not neutralize or validate arguments before execution, allowing attackers to pass unexpected arguments to downstream command-line processes.
A symbolic link directory traversal vulnerability was identified in go-git, a pure Go implementation of the Git specification. This vulnerability allows an attacker to construct a repository that, when checked out or processed, bypasses directory boundaries to write or overwrite arbitrary files on the host filesystem.
CVE-2026-71557 is a path traversal vulnerability in go-git, a pure-Go implementation of Git. In vulnerable versions, the filesystem-backed storage engine fails to validate reference names before mapping them to on-disk paths. An attacker hosting a malicious Git server can advertise references containing directory traversal sequences, such as 'refs/heads/../../config', to write or overwrite files outside the intended reference storage directory.
An information disclosure vulnerability in the Nuxt development server allows adjacent network attackers to retrieve the absolute project root directory and a persistent workspace UUID by querying the unprotected Chrome DevTools workspace endpoint. This occurs when the development server is bound to a network-reachable interface, allowing requests that bypass the header-based security verification checks.
A Regular Expression Denial of Service (ReDoS) vulnerability exists in SvelteKit's content negotiation header parser prior to version 2.70.2. An unauthenticated remote attacker can exploit this vulnerability by sending a crafted Accept header with highly repetitive malformed values. This triggers catastrophic backtracking on the single-threaded Node.js/Bun event loop, leading to CPU exhaustion and full denial of service.
An OS command injection vulnerability exists in the npm package loading component of the jsii-diff CLI tool within the AWS jsii framework. Prior to version 1.131.0, when parsing package specifiers prefixed with `npm:`, the tool concatenated user-controlled inputs directly into a shell execution string via child_process.exec. This allows attackers to execute arbitrary shell commands under the context of the running Node.js process.
CodeIgniter4 versions prior to v4.7.4 contain a protocol-spoofing vulnerability due to improper verification of upstream reverse proxy forwarding headers. Remote, unauthenticated attackers can inject headers like X-Forwarded-Proto to deceive the framework into identifying an insecure HTTP request as a secure HTTPS connection.