Releases: umanio-agency/skillctl
0.2.0 - 2026-05-27
Release Notes
Added
skillctl remove— remove skills from the current project. Lists every removable skill (installed via skillctl, created locally, or an orphaned.skills.tomlentry whose folder is already gone), with each kind distinguished in the selection list, and lets you pick by interactive multi-select, by name (--skill <name>, repeatable), or all at once (--all). Deletes the selected skill folders and drops their.skills.tomlentries;.skills.tomlis only rewritten when a tracked entry actually changes. The command is project-only — it never touches the library or git.
Security
- The removal path refuses to follow a symlink: the destination's type is re-checked immediately before deletion, so a folder swapped for a symlink cannot redirect the recursive delete outside the project (closes a TOCTOU window surfaced by the pre-release audit). A symlinked destination is treated as "no folder on disk" — only its manifest entry can be dropped, never deleted through.
--skill <name>fails closed: an unknown name errors, and a name shared by two skills errors as ambiguous rather than silently deleting the wrong folder. The project root,.git, and.skills.tomlcan never be selected for deletion.
skillctl remove was reviewed before release with the project's standard multi-agent security audit pass (FS-safety / untrusted-input / logic dimensions). 5 new unit tests; cargo test: 158 pass; clippy clean; cargo audit clean.
Install skillctl 0.2.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/umanio-agency/skillctl/releases/download/v0.2.0/skillctl-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/umanio-agency/skillctl/releases/download/v0.2.0/skillctl-installer.ps1 | iex"Install prebuilt binaries via Homebrew
brew install umanio-agency/tap/skillctlDownload skillctl 0.2.0
| 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 |
0.1.8 - 2026-05-25
Release Notes
Security & robustness
Close Phase 9.1 — five of the eight LOW findings that were explicitly deferred at v0.1.6. The remaining three (L11 cache-slug uniqueness, L14 fork destination prompt, plus the not-yet-numbered "deferred-with-reason" items from §10 of the internal audit) are gated on broader UX or migration decisions that warrant their own designs.
- APFS case-insensitive collision warning (L6). Two skills named
Fooandfooare distinct under skillctl's identifier-class validation (case-significant) but collapse to the same path on case-insensitive filesystems (APFS-CI on macOS external drives, HFS+, NTFS), so a subsequentaddwould silently clobber one with the other.skill::discovernow groups skills by their lowercased name and surfaces a warning per collision group. Doesn't reject — case-insensitivity is host-dependent and operators on case-sensitive ext4/APFS-CS are fine — but lets the operator notice before something disappears. - Homograph / mixed-script name warning (L3). New
unicode-scriptdependency (~20 KB).skill::discoverchecks each accepted skill name for characters spanning two or more distinct Unicode scripts (ignoringCommon/Inherited/Unknown— digits, punctuation, emoji are exempt). A name likeclаude(where theаis U+0430 Cyrillic, not U+0061 Latin) raises a warning so the operator can spot a homograph attack from a malicious library that publishes a skill visually indistinguishable from a legitimate one. - NFC normalisation of paths in dedup (L5). New
unicode-normalizationdependency (~100 KB).path_safety::normalize_lexicalnow Unicode-NFC-normalises every UTF-8 path component before returning. macOS HFS+ stores filenames in NFD (decomposed:é=e+ combining acute) while Linux stores NFC (one codepoint); without this, the lexical dedup indetectand thesafe_joincomparisons treat the same logical filename as two distinct paths when the project crosses platforms. Non-UTF-8 components pass through unchanged. skill::discovernow returns warnings instead ofeprintln!(infrastructure fix). The oversize-SKILL.md warning added in v0.1.6 usedeprintln!, which bypasses--jsongating.discovernow returns aDiscoverOutput { skills, warnings }; callers inadd/list/detectroute each warning throughui::log_warning, which silently no-ops in--jsonmode. Closes a latent v0.1.6 footgun and makes L3 + L6 warnings JSON-safe at the same time.actions/attest-build-provenance@v3on every release (L10). New.github/workflows/attest.ymltriggers on the GitHub Releasepublishedevent, downloads every binary asset, and generates a SLSA build-provenance attestation signed by GitHub Actions and recorded in Sigstore's transparency log. Users can verify a binary they downloaded withgh attestation verify <file> --repo umanio-agency/skillctl— a mismatch or missing attestation means the artifact wasn't produced by this repo's release workflow. Standalone workflow (not threaded into cargo-dist's generatedrelease.yml) so it survivescargo dist initregenerations.- Dependency policy documented (L9).
CONTRIBUTING.mdgains a "Dependency policy" section spelling out the caret-semantics convention, theCargo.lock-as-pinning trust path,cargo auditas a release gate, the no-auto-update transitive policy, and the response plan if a dep ships a semver-incorrect break.
12 new unit tests (4 case-collision/homograph in skill::tests, 5 mixed-script unit cases, 1 NFC equality in path_safety::tests, 2 collision-suppression). cargo test: 153 pass; clippy clean; cargo audit clean.
Deferred to a future release (with reasons):
- L11 (cache-slug uniqueness via hash suffix). Pre-v1 with one-library-at-a-time the slug collision is theoretical; revisit if/when multi-library support lands or two upstream
<owner>namespaces produce a real conflict on the same dev machine. - L14 (prompt operator on fork destination instead of inheriting source parent). UX question worth a dedicated
forkflow review rather than a one-line nudge.
Install skillctl 0.1.8
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/umanio-agency/skillctl/releases/download/v0.1.8/skillctl-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/umanio-agency/skillctl/releases/download/v0.1.8/skillctl-installer.ps1 | iex"Install prebuilt binaries via Homebrew
brew install umanio-agency/tap/skillctlDownload skillctl 0.1.8
| 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 |
0.1.7 - 2026-05-25
Release Notes
Release engineering
Re-ship v0.1.6's content with the release pipeline unblocked. v0.1.6 was tagged but its cargo-dist plan job failed because the manual submodules: recursive → false edit (Phase 8.4 L12) in .github/workflows/release.yml made dist host --steps=create refuse to run ("out of date contents and needs to be regenerated"). v0.1.6 is on crates.io but has no GitHub Release artifacts and no Homebrew tap update; v0.1.7 fixes that.
allow-dirty = ["ci"]indist-workspace.toml. cargo-dist exposes an explicit allow-list to tolerate manual edits in the workflow it generates. Addingcito it preserves the L12 defense-in-depth (no recursive submodule pull from the release runner) while lettingdist hostproceed.
No code or test changes from v0.1.6.
Install skillctl 0.1.7
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/umanio-agency/skillctl/releases/download/v0.1.7/skillctl-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/umanio-agency/skillctl/releases/download/v0.1.7/skillctl-installer.ps1 | iex"Install prebuilt binaries via Homebrew
brew install umanio-agency/tap/skillctlDownload skillctl 0.1.7
| 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 |
0.1.5 - 2026-05-22
Release Notes
Security & robustness
Close the comprehensive audit's Phase 8.3: 13 MEDIUM findings plus the deferred push-side half of H8. No item here is single-shot exploitable, but each closes a credibility-eroding leak (credentials in logs), DoS vector (unbounded parsers, recursive walkers), or footgun (silently-discarded state, hook execution via shared cache).
- Credentials stripped from stored
library.url(M1).skillctl init https://x-access-token:<PAT>@github.com/...would store the full URL — token and all — inconfig.toml, then echo it back in JSON output, error chains, and CI logs.initnow sanitises the URL (stripsuser[:password]@fromhttps:///http://authority sections) before persisting; the one-timegit clonestill uses the original URL for authentication, but the token never lands on disk or in any later command's output. SSH forms (git@host:...,ssh://git@host/...) are unchanged. - Git stderr scrubbed in every error chain (M3). Each
git-shell-out site usedString::from_utf8_lossy(&stderr).trim()— which would faithfully echo credential-helper banners, proxy URLs containing PATs, ANSI control sequences, and stack traces past the first line. The newgit::scrub_stderrhelper takes the first non-empty line, strips C0/C1/DEL/ESC control bytes, and redacts known token prefixes (ghp_*,gho_*,ghs_*,ghu_*,github_pat_*,x-access-token:*) to<prefix>***. Applied uniformly across every git invocation. core.hooksPathneutralised on every git call (M12). The library cache is a git repo whose.git/configis reachable from inside skill content. A malicious library that dropped a script at the operator's globally-configuredcore.hooksPathwould have it executed by anygit commitin the cache. EveryCommand::new("git")now goes through agit_cmd()helper that prepends-c core.hooksPath=/dev/null, so hook execution is impossible regardless of global or in-cache git config.git status --porcelaincheck beforereset --hard @{upstream}(M10).fetch_and_fast_forwardused to unconditionallygit reset --hard @{upstream}, silently destroying any uncommitted state left over from a previous skillctl run that crashed mid-commit (e.g.replace_folder_contentssucceeded butgit pushfailed). Now refuses to refresh when the cache reports any porcelain output, surfacing a clear "uncommitted changes — inspect withgit -C <cache> status" message so the operator can investigate before any destruction happens.- Frontmatter parser bounded at 200 lines (M4). A SKILL.md with an opening
---but no closing fence would force the parser to scan the entire (potentially multi-GiB) body — a cheap DoS reachable on everyskill::discovercall. Capped toMAX_FRONTMATTER_LINES = 200; unterminated frontmatter is now treated as "no frontmatter" (the skill is dropped from discovery rather than half-parsed). validate_fork_namerejects control characters and caps length (M5). The previous fork-name validator only rejected empty /./../ path separators — a name likefoo\0barwould panic insideCString::newwhen later passed toCommand. Now rejects any control char (NUL, ESC, ANSI, DEL, newline, CR, tab) and caps at 64 bytes. Consolidated assanitize::validate_fork_name(was duplicated betweenpush.rsandpull.rs)..skills.tomlrejects unknown fields, duplicates, and overflow (M6). Added#[serde(deny_unknown_fields)]onProjectConfigandInstalledSkill, so a malicious PR can no longer smuggle unknown keys (which might later be load-bearing for an unreleased feature) into the deserialiser. Duplicatenameordestinationentries are rejected at load — silent dedup would make every command ambiguous about which entry wins. Capped at 256 entries to bound the diff-classifier work.copy_dir_allis iterative and masks mode bits (M7 + M8). Converted from recursion to an explicitVec<(PathBuf, PathBuf)>work stack, so an adversarial skill with 10k-deep nesting can no longer blow Rust's default 8 MiB thread stack. On Unix, copied file modes are now masked to0o644 | (src_mode & 0o100)— only the user-execute bit propagates; setuid, setgid, sticky, group-write, world-write, group-execute and world-execute are stripped. A library that drop-ins a setuid binary cannot weaponise the round-trip into elevated privileges on the destination.detectdedup unions canonical AND lexical comparison (M9). The "already installed" set was built fromfs::canonicalizeonly — silently dropping entries whose destination had been deleted from disk. An attacker who removed.claude/skills/foo/and dropped a replacement at the same path would have it re-detected as a new skill on the nextdetect. Now compares by canonical path (when both ends exist) AND lexical path (covers the deleted-destination case via the newpath_safety::normalize_lexicalhelper).detectwalker respects.gitignoreand skips vendor dirs by default (M11). A malicious npm package shipping its ownSKILL.mdundernode_modules/...could be picked up byskillctl detect --allrunning in CI and uploaded to the library.skill::discovernow takes aninclude_vendoredparameter; the default (false) leans onignore::WalkBuilder's.gitignore/.ignorerespect plus a hard-skip onnode_modules/target. New CLI flagskillctl detect --include-vendoredfor the explicit opt-in.- Homebrew tap typo-squat documented (M13). Both README and SECURITY.md now prominently call out the canonical fully-qualified install (
brew install umanio-agency/homebrew-tap/skillctl) and explain that anyone can ship askillctl.rbformula under their ownhomebrew-taprepo. Pinning the owner avoids the typo-squat risk. push --allcontinues on per-skill failure (H8 push-side). The pre-v0.1.5 apply loop used?inside the per-skill body, so one failing skill aborted the entire batch and orphaned the cache's working tree for the successful early skills (commit + push never happened, cache stayed dirty until the nextfetch_and_fast_forwardreset it). Now each apply is wrapped in an IIFE: on per-skill failure, the change is rolled back withgit checkout HEAD -- <library_relative>, a warning is logged, and the loop continues. If all skills fail, the command exits cleanly with "nothing pushed". This closes the half of H8 deferred from v0.1.4.
13 new unit tests added (3 path_safety lexical normalisation, 3 sanitize fork-name hardening, 4 .skills.toml deny/dedup/cap, 3 discover gitignore/node_modules/include-vendored, 2 frontmatter bound, 7 git stderr scrub, 3 fs_util mode-mask + deep nesting). cargo test: 136 pass; clippy clean; cargo audit clean.
Install skillctl 0.1.5
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/umanio-agency/skillctl/releases/download/v0.1.5/skillctl-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/umanio-agency/skillctl/releases/download/v0.1.5/skillctl-installer.ps1 | iex"Install prebuilt binaries via Homebrew
brew install umanio-agency/tap/skillctlDownload skillctl 0.1.5
| 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 |
0.1.4 - 2026-05-22
Release Notes
Security & robustness
Close the seven HIGH atomicity / concurrency / DoS findings from the comprehensive audit's Phase 8.2. The headline items are not exploitable by an external attacker on a single-user box, but each represents a real data-loss or denial-of-service scenario under realistic conditions (Ctrl-C mid-operation, two concurrent skillctl runs, a malicious .skills.toml PR with an orphan source_sha).
- Atomic
replace_folder_contents. The copy primitive used byadd/pull/pushnow stages new content into a uniquely-named sibling of the destination, moves the old destination aside into a backup sibling, then atomically renames the staging dir over the destination. At any crash point, either the old or the new content is in place — never a half-written tree. Rolls the backup back if the final rename fails. Closes three HIGH findings (H5, H6, H7) with one primitive. - Atomic
.skills.tomlsave.project_config::savewrites to a sibling temp file thenfs::renames it over the target — a crash mid-write only leaves the temp file on disk, never a truncated.skills.toml. Used by every command that mutates the tracked-skills index. - Process-level locking on the library cache and
.skills.toml. Newsrc/lock.rsprovidesacquire_exclusive(dir, what)backed byfs4's cross-platformtry_lock_exclusive. Every command that touches the library cache (list/add/push/pull/detect) holds an exclusive lock on<cache>/.skillctl.lockfor the fullgit fetch → mutate → pushcritical section; every command that mutates.skills.tomladditionally locks<cwd>/.skillctl.lock. A second concurrentskillctlinvocation fails fast withAppError::Conflict("another skillctl is running") rather than racing on.git/index.lock. Closes H3 + H4. pushsaves.skills.tomlbefore any local rename. Post-git push, the apply loop is now split into three phases: in-memory mutations, atomic save, then local renames (now non-fatal). A Ctrl-C between push and save used to leave.skills.tomlreferencing the oldsource_sha, which the next run would reclassify asLibraryAheadand offer to wipe local edits silently. The new ordering reduces the failure window to "disk full or EACCES at save time"; local rename failures degrade to a warning ("library updated but local rename failed — rename the local folder by hand") rather than dropping the SHA mapping. Closes H6.pullfork-locally is now atomic. The pre-v0.1.4 sequence (fs::renameoriginal aside, thencopy_dir_alllibrary version) could lose the original on a mid-copy failure (rename succeeded, copy failed, original gone, library version not yet present). Rewritten with the same tempdir-swap pattern asreplace_folder_contentsvia the newfs_util::swap_with_bakhelper. Closes H7.- Orphan
source_shais per-skill, not a batch DoS. A malicious.skills.tomlentry withsource_sha = "0000…"(a valid-hex but unknown commit) used to makeclassifyreturnErrat the first such entry and abort the entire batch — weaponisable to DoS every other skill in the samepull --all/push --allrun.git::ls_tree_blobsnow returnsResult<Option<HashMap>>, withOk(None)for an unknown refspec; the classifier surfaces this as a newSkillStatus::SourceShaOrphanedvariant, andpush/pulllog a per-skill warning ("source_sha doesn't resolve in the library; skipping") while continuing with the rest. Closes H9. pull --allcontinues on per-skill failure. The apply loop now wraps each skill in an IIFE that logs a warning on error and continues..skills.tomlis saved at the end regardless, so successful per-skillsource_shaupdates persist even when a sibling apply fails. Closes H8 (pull side). The push-side equivalent (one-commit-per-run cleanup-on-failure) is deferred to a follow-up release.
3 new unit tests cover the atomic-replace contract (failure preserves dst, failure cleans up staging, swap_with_bak round-trip); 2 new tests cover the lock primitive. cargo test: 100 pass; clippy clean; cargo audit clean. New runtime dependency: fs4 = "0.13.1" (advisory file locks).
Install skillctl 0.1.4
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/umanio-agency/skillctl/releases/download/v0.1.4/skillctl-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/umanio-agency/skillctl/releases/download/v0.1.4/skillctl-installer.ps1 | iex"Install prebuilt binaries via Homebrew
brew install umanio-agency/tap/skillctlDownload skillctl 0.1.4
| 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 |
0.1.3 - 2026-05-21
Release Notes
Security
Fix five additional vulnerabilities surfaced by a comprehensive multi-angle audit (six parallel sub-agents, each covering one threat-model dimension: command injection, input parsing, FS safety 2nd pass + concurrency, output safety + agent-mode JSON, supply chain, logic / state-machine). These were independent of the firebaguette audit that motivated v0.1.2; together they close every CRITICAL and offensive HIGH finding identified by the audit.
source_shaargument injection ingit ls-tree(CRITICAL, four agents converged on this).InstalledSkill.source_shadeserialized from.skills.toml(committed, PR-mergeable) flowed unvalidated intogit ls-tree -r -z <refspec> -- <path>. Because the refspec sits before--, an attacker who slipped a malicious.skills.tomlinto a PR could setsource_sha = "--name-only"/--abbrev=0/--output=…and corrupt the diff classifier — which drivespull/pushdestructive decisions — or forge divergence state to trickpush --on-divergence overwriteinto clobbering the wrong content.InstalledSkill::validatenow rejects anysource_shathat isn't 40–64 hex characters (sha1 / sha256).- FIFO / device / socket DoS in
copy_dir_all(CRITICAL). The file-type branch only checkedis_dir()/is_symlink(); a FIFO inside a skill folder fell through tofs::copy, which blocks indefinitely waiting for a writer. A character device like/dev/zerowould read until OOM. Nowcopy_dir_allonly allows regular files and directories; anything else (FIFO, socket, device) is rejected withAppError::Config. add --destarbitrary-directory wipe in agent mode (HIGH).--destaccepted absolute paths and..traversal without validation, soskillctl add --dest /Users/victim/.ssh --on-conflict overwrite --skill <maliciously-named>would wipe arbitrary directories in one shot from any agent-driven invocation. Now--destrejects..unconditionally, and rejects absolute paths when running in non-interactive /--jsonmode (where the operator may be an LLM running on attacker-supplied input). Interactive use is unchanged.- Commit-message trailer forgery via skill names (HIGH). Skill names were spliced verbatim into
git commit -m "update skill: <name>"and into thecommit.messagefield of--jsonoutput. A library skill with a\nin its name (e.g.foo\nCo-Authored-By: evil@x) produced a forged trailer that downstream tooling (Linear, GitHub commit-bot, release-notes scrapers) would treat as real authorship metadata. The newsanitizemodule strict-validates everyname/tag(identifier-class: no control bytes, no newlines, no ESC) and lenient-validatesdescription/--message(allows\n/\t, rejects\r/ DEL / C0+C1 controls). Skills with poisoned names are dropped silently fromdiscover(a poisoned name can't be safely displayed either); poisoned tags or descriptions are stripped from otherwise-valid skills. - Hardlink exfiltration via the round-trip (HIGH).
fs::symlink_metadatareports a regular file for hardlinks (shared inode), andfs::copyreads the target content. An untrusted agent writing<project>/my-skill/dataas a hardlink to~/.ssh/id_rsawould have shipped the SSH key content to the library on the nextskillctl pushordetect.copy_dir_allnow checksnlink() > 1on regular files (Unix) and refuses to copy hardlinked content with the same fail-closed philosophy as symlinks.
Audit methodology and the full remaining backlog (10 MEDIUM + 18 LOW spread across atomicity, concurrency, output hardening, supply chain documentation) are tracked privately and will be addressed in 0.1.4 / 0.1.5. 23 new unit + integration tests cover each rejection class; cargo test: 95 pass; clippy clean; cargo audit clean.
Install skillctl 0.1.3
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/umanio-agency/skillctl/releases/download/v0.1.3/skillctl-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/umanio-agency/skillctl/releases/download/v0.1.3/skillctl-installer.ps1 | iex"Install prebuilt binaries via Homebrew
brew install umanio-agency/tap/skillctlDownload skillctl 0.1.3
| 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 |
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 |
0.1.1 - 2026-05-11
Release Notes
Added
- Published on crates.io:
cargo install skillctlnow works. - Pre-built binaries on GitHub Releases for macOS (x86_64, aarch64), Linux (x86_64, aarch64), and Windows (x86_64), built via
cargo-dist. - Homebrew tap at
umanio-agency/homebrew-tap:brew install umanio-agency/homebrew-tap/skillctl. - Shell + PowerShell
curl | sh-style installers wired into the release workflow.
Changed
- Crate renamed from
skills-clitoskillctlto publish on crates.io (theskills-clicrate name was already taken by an unrelated package). - GitHub repository renamed from
umanio-agency/skills-clitoumanio-agency/skillctl. GitHub redirects from the old URL still work for inbound links. - Companion skill folders moved:
.claude/skills/skills-cli-{project,usage}/→.claude/skills/skillctl-{project,usage}/. - Config and cache paths (
dev.umanio-agency.skills-cli,~/.config/skills-cli/,~/.cache/skills-cli/) intentionally kept to avoid breaking existing local state for no user-facing gain.
Install skillctl 0.1.1
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/umanio-agency/skillctl/releases/download/v0.1.1/skillctl-installer.sh | shInstall prebuilt binaries via powershell script
powershell -ExecutionPolicy Bypass -c "irm https://github.com/umanio-agency/skillctl/releases/download/v0.1.1/skillctl-installer.ps1 | iex"Install prebuilt binaries via Homebrew
brew install umanio-agency/tap/skillctlDownload skillctl 0.1.1
| 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 |
skillctl v0.1.0
What is this?
The contributor-side companion to npx skills (vercel-labs/skills). Round-trip your edits between a project and your personal Claude skills library:
push— propagate local edits back to the library, with fork-as-new on divergence.detect— find skills you wrote locally and contribute them upstream.pull— refresh installed skills; fork-locally on divergence preserves your edits under a new name (addresses vercel-labs/skills#455).add— multi-select install with a live-filter prompt; recordssource_shain.skills.tomlso the round-trip diff can classify changes.
Plus tag filtering everywhere (--tag, --all-tags), --json output with stable per-command schemas, granular exit codes, and a fully non-interactive mode for agents.
See the README and the Comparison with `npx skills` section for how the two tools fit together.
Install
```sh
git clone https://github.com/umanio-agency/skills-cli.git
cd skills-cli
cargo install --path .
```
Requires Rust 1.85+ (edition 2024) and `git` on `PATH`. The binary is `skillctl`.
Full changelog
See CHANGELOG.md.