Skip to content

0.1.2 - 2026-05-20

Choose a tag to compare

@github-actions github-actions released this 20 May 09:33

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) bypassed entry.file_type().is_dir(), fell into the file branch, and was dereferenced by fs::copy — copying the symlink target into the project. A subsequent skillctl push would have published the secret to the (possibly public) library. Symlinks are now hard-rejected by copy_dir_all at both the top-level source and any descendant entry, and replace_folder_contents refuses a symlinked destination so remove_dir_all cannot be tricked.
  • Path traversal via destination and source_path in .skills.toml. Both fields were deserialized as PathBuf with zero validation. Because Path::join lets an absolute right-hand side replace the base, a .skills.toml entry like destination = "/home/seb/.ssh" made cwd.join(...) resolve outside the project and replace_folder_contentsremove_dir_all wipe arbitrary directories. .. traversal was equally unguarded. New InstalledSkill::validate runs at project_config::load time and rejects absolute paths, .., and Windows-prefix components for both fields; the same check is wired (defense-in-depth) at every destructive call site in push.rs / pull.rs via the new path_safety::safe_join helper.
  • detect --target accepted .. even though it rejected absolute paths. Validation in commands::detect::resolve_target now goes through the same validate_relative_subpath helper, rejecting any non-Normal/CurDir component. The interactive "custom path" prompt was tightened to match.
  • Fork-name validation accepted . and .. literally. validate_fork_name in both push.rs and pull.rs only rejected / and \, so a fork named .. would have produced a Path::join resolving to the parent directory, then fs::rename could 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.md convention (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 | sh

Install 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/skillctl

Download 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