0.1.2 - 2026-05-20
Release Notes
Security
Fix four path-safety vulnerabilities that, in combination, allowed a malicious skills library or a crafted .skills.toml (e.g. mergeable via PR) to exfiltrate arbitrary files through the round-trip (read on skillctl add, leak on skillctl push) and to delete arbitrary directories outside the project or library root on skillctl pull / push / detect. Reported privately on 2026-05-19 by firebaguette via the Umanio Discord; all four issues are addressed in this release.
- Symlink follow in
fs_util::copy_dir_all. A symlink inside a skill folder (e.g.niania → /home/user/.aws/credentials) bypassedentry.file_type().is_dir(), fell into the file branch, and was dereferenced byfs::copy— copying the symlink target into the project. A subsequentskillctl pushwould have published the secret to the (possibly public) library. Symlinks are now hard-rejected bycopy_dir_allat both the top-level source and any descendant entry, andreplace_folder_contentsrefuses a symlinked destination soremove_dir_allcannot be tricked. - Path traversal via
destinationandsource_pathin.skills.toml. Both fields were deserialized asPathBufwith zero validation. BecausePath::joinlets an absolute right-hand side replace the base, a.skills.tomlentry likedestination = "/home/seb/.ssh"madecwd.join(...)resolve outside the project andreplace_folder_contents→remove_dir_allwipe arbitrary directories...traversal was equally unguarded. NewInstalledSkill::validateruns atproject_config::loadtime and rejects absolute paths,.., and Windows-prefix components for both fields; the same check is wired (defense-in-depth) at every destructive call site inpush.rs/pull.rsvia the newpath_safety::safe_joinhelper. detect --targetaccepted..even though it rejected absolute paths. Validation incommands::detect::resolve_targetnow goes through the samevalidate_relative_subpathhelper, rejecting any non-Normal/CurDircomponent. The interactive "custom path" prompt was tightened to match.- Fork-name validation accepted
.and..literally.validate_fork_namein bothpush.rsandpull.rsonly rejected/and\, so a fork named..would have produced aPath::joinresolving to the parent directory, thenfs::renamecould have clobbered it..and..are now explicit rejections.
Threat-model note: the fix is purely lexical (component-level) plus an explicit symlink check at copy time. No filesystem canonicalize calls were added, avoiding TOCTOU windows and keeping the validation pure-functional (AppError::Config, exit code 2). 34 new unit tests cover each rejection class and each attack scenario end-to-end.
Changed
- README and crate description reframed around "agent skills" terminology to reflect the multi-tool nature of the
SKILL.mdconvention (Claude Code, Codex, Cursor, OpenCode, and others in the open agent skills ecosystem) — no behavior change.
Install skillctl 0.1.2
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/umanio-agency/skillctl/releases/download/v0.1.2/skillctl-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/umanio-agency/skillctl/releases/download/v0.1.2/skillctl-installer.ps1 | iex"Install prebuilt binaries via Homebrew
brew install umanio-agency/tap/skillctlDownload skillctl 0.1.2
| File | Platform | Checksum |
|---|---|---|
| skillctl-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| skillctl-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| skillctl-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| skillctl-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| skillctl-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |