Releases: t4sh/skills4sh
v0.4.7 — close 6 internal code-review findings
What's new — close 6 internal code-review findings
One critical safety bug fix (size cap was bypassable), four polish items, one documentation gap. No breaking changes for normal users.
Critical safety fix
Per-file size cap is now stream-enforced. v0.4.6 advertised a 50 MiB cap but implemented it by checking buffer length AFTER await res.arrayBuffer() — which reads the entire response into memory first. An attacker-controlled server omitting Content-Length (or sending chunked transfer with an unbounded body) could OOM the process before any check fired.
v0.4.7 streams the body via res.body.getReader() with a running byte counter. The stream is aborted as soon as the cap is exceeded:
✗ skills/foo/SKILL.md exceeds 52428800 byte size cap
(streamed 53477376 bytes before abort; honest Content-Length absent or false).
Refusing to buffer further.
The Content-Length check is kept as an upfront optimization (rejects without ever opening the stream) but is no longer the only line of defense.
Polish
- YAML block-scalar
reason:fields supported —parseExpectedFindingscorrectly handles literal (|), folded (>), and chomping (|-/>-) styles. Multi-line rationale text is captured fully (was being silently truncated to the marker character, which then failedvalidateAcknowledgedReasonswith a confusing "1 char" error). --versionis strict — must be the sole argument.skills4sh --skill foo --versionis now rejected instead of silently exiting 0 with just the version.--helpdocuments the v0.4.6 dry-run/verify behavior —--dry-runnow hits the network to fetch the lockfile (one extra call); useful info for users near rate limits.
Documentation
BRANCH_PROTECTION.mdrecovery escape hatch — now thatenforce_admins: trueblocks admin direct push, a broken-required-check workflow could lock the maintainer out. The doc explains the temporary-disable / push-fix / re-enable cycle viagh api.
Tests
161 total (was 153, +8): streaming-abort, 5 block-scalar parsing cases, 2 strict---version cases.
Verify
npm install skills4sh@0.4.7
npm audit signatures skills4sh@0.4.7
npx skills4sh --version # → 0.4.7
npx skills4sh --skill foo --version # → exits 1 (was 0 in v0.4.6)Full changelog: v0.4.6...v0.4.7
v0.4.6 — 11 fresh-audit findings closed
What's new — 11 fresh-audit findings closed
3 real security/safety fixes, 3 UX polish items, 4 documentation fixes, 1 dry-run/verify order bug discovered while testing. One behavior change worth flagging: skills4sh against a repo with no skills-lock.json now errors out instead of silently skipping verification.
Security / safety
Missing lockfile is now a hard error. Pre-v0.4.6 silently skipped verification with a warning if a repo had no skills-lock.json. That was a downgrade-attack vector — an attacker who pushed a malicious commit removing the lockfile would silently disable hash verification on the next install of that ref. New behavior:
$ npx skills4sh --skill foo --repo malicious/repo
✗ skills-lock.json not present in malicious/repo@main.
Without a lockfile, hash verification cannot proceed. Either:
(a) the source repo needs a skills-lock.json checked in, or
(b) you can pass --no-verify to skip verification (INSECURE — only for
local testing or repos you fully trust without integrity checks).
Per-file 50 MiB size cap. fetchRaw now checks Content-Length before reading the body and the actual buffer length after. Defensive against --repo installs pointed at attacker-controlled sources (bundled t4sh/skills4sh skills are hash-pinned so they can't grow silently anyway).
acknowledged: true expected_findings require non-empty reason:. New validateAcknowledgedReasons() enforces a 20-char minimum on rationale text. Prevents rubber-stamping acknowledged: true without explanation. Wired into drift-check per skill.
Dry-run/verify order fix. Pre-v0.4.6 dry-run returned BEFORE verifyAgainstLock(), so --dry-run on a repo with a missing lockfile would falsely report "would install" while a real install would fail. Verification now runs first; dry-run reflects real-install outcome.
UX polish
--version/-vflag — prints version to stdout, exits 0.- Quieter parse-error path — no more dumping full help on every unknown flag.
- Dry-run error envelope — errors in dry-run mode emit JSON envelope on stdout:
{ schemaVersion: 1, command, dryRun: true, error: { message, code, status } }.
Documentation
SECURITY.md "Known Non-Issues" section — pre-empts four audit concerns that audit cycles repeatedly raise but don't apply to this codebase:
- Symlinks inside skill bundles — the installer only calls
writeFile(), neversymlink(). A symlink in the source repo arrives as a regular file containing the link-target string. - Timing-safe hash comparison — irrelevant in this context (no remote oracle).
- Dependabot drift-skip — accepted tradeoff; daily cron catches drift within 24h regardless.
SKIP_BIN_TAG_PARITY=1— local-publish-only escape hatch; CI publish doesn't honor it because OIDC binding is keyed to the workflow file path.
CHANGELOG.md historical fix — v0.3.10 entry said "6 workflow files" but the actual count was 7.
Tests
138 → 153 (+15 covering reason validation, --version, parse-error quietness, dry-run error envelope, lockfile-404 hard error, lockfile-404 + --no-verify opt-in, oversized-file cap).
Verify
npm install skills4sh@0.4.6
npm audit signatures skills4sh@0.4.6
npx skills4sh --version # → 0.4.6 (to stdout)
npx skills4sh --skill x --repo y/z # → fails hard if y/z has no skills-lock.jsonFull changelog: v0.4.5...v0.4.6
v0.4.5 — six fresh-eyes audit findings closed in one PR
What's new — six fresh-eyes audit findings, one PR
Four CLI safety + UX fixes (items 1-4), two test-coverage fills for the verifiers themselves (items 5-6). No breaking change for install/list flows. One behavior change: remove --all now requires explicit --yes confirmation.
CLI safety + UX
1. remove --all requires --yes. Previously, npx skills4sh remove --all silently wiped every installed skill in <dest> — no prompt, no confirmation. Typo of remove agent-memory as remove --all was catastrophic. New behavior:
$ npx skills4sh remove --all
✗ remove --all requires explicit confirmation. Re-run with --yes:
skills4sh remove --all --yes --dest ~/.claude/skills
(the --yes is required because --all wipes every installed skill in <dest>)
2. --force in remove is now actually implemented. The flag was previously documented as "NOT IMPLEMENTED in this version." Three uses now:
- Clean up a half-installed directory missing its SKILL.md gate
- Unlink a symlink without following it (target preserved)
- Rejected with
--all— bulk + force defeats the safety net
3. Versioned --dry-run JSON schema. Both install and remove now emit:
{
"schemaVersion": 1,
"command": "install",
"dryRun": true,
"skill": "agent-memory",
"computedHash": "...",
"source": { "owner": "t4sh", "repo": "skills4sh", "ref": "main" }
}Downstream tooling that parses dry-run output now has a stable contract. Future fields don't silently break parsers.
4. Symlink-safe remove. removeSkill uses lstat to detect symlinks BEFORE rm. Default behavior on encountering a symlink: refuse. With --force: unlink the symlink itself only, never follow it. Closes scenarios where ~/.claude/skills/foo symlinked elsewhere could be silently destroyed by recursive removal.
Test coverage — fills the "verifiers verified" gap
5. bin/drift-check.mjs is now importable. Wrapped in export async function runDriftChecks(rootDir). Tests can exercise the full check pipeline against fixture repos in tmpdirs without spawning subprocesses. The CLI invocation pattern is unchanged.
6. Markdown-link validation in skills. Every relative-path link in a *.md file under each skill must resolve to a file in the skill's inventory. External URLs, mailto:, in-page anchors, and links inside fenced code blocks are correctly excluded. Links escaping the skill directory (e.g., ../../etc/passwd) are rejected. The four bundled skills all pass.
Tests
- +20 net (118 → 138)
- New
tests/drift-check.test.mjs(12 tests): fixture-based e2e covering clean baseline, hash mismatch, version mismatch, doc-sync failures, schema-invalid manifests, and 6 markdown-link scenarios tests/integration.test.mjs(+6 tests): new envelope shape,remove --all --yesrequirement, symlink refusal,--forcepathstests/installer.test.mjs(+5 tests):--yescapture,-yshort form, existing test updated for new semantics
Verify
npm install skills4sh@0.4.5
npm audit signatures skills4sh@0.4.5
skills4sh remove --all # should fail with --yes-required message
skills4sh --skill agent-memory --dry-run | jq .schemaVersion # → 1Full changelog: v0.4.4...v0.4.5
v0.4.4 — strip scripts from registry metadata too
What's new — closes the metadata half of the v0.4.3 fix
v0.4.3 stripped dev scripts from the tarball (consumer install path: clean). But npm view skills4sh@0.4.3 scripts and the raw registry endpoint still showed them — because npm publish reads package.json twice and the postpack restore happened between the two reads.
v0.4.4 closes that.
Fixed
postpack now checks process.env.npm_command. When it's "publish", restore is deferred to a new postpublish hook that runs after the registry metadata is sent. For plain npm pack, behavior is unchanged.
Verify after release
# Tarball — already clean since v0.4.3:
curl -sL https://registry.npmjs.org/skills4sh/-/skills4sh-0.4.4.tgz | tar -xzO package/package.json | jq .scripts
# → null
# Registry metadata — now also clean since v0.4.4:
curl -sL https://registry.npmjs.org/skills4sh/0.4.4 | jq .scripts
# → null
npm view skills4sh@0.4.4 scripts
# → empty / absentTests
113 → 118 (+5 covering the publish-vs-pack distinction, including a full publish simulation).
Full changelog: v0.4.3...v0.4.4
v0.4.3 — closes three parallel-audit findings
What's new — three parallel-audit findings closed
No public CLI surface change. All three fixes are about closing latent gaps in the existing controls.
Fixed
1. bin/install.mjs: SIGINT/SIGTERM cleanup no longer deletes the backup mid-update.
v0.4.2's cleanup handler unconditionally rmd both stagingDir and backupDir on signal. If a signal arrived during the narrow "backed-up" window (after the old skill was moved aside, before the new content was promoted), the backup — the user's only intact copy — would be destroyed. New behavior: state-aware cleanup via a pure interruptCleanupPlan(state) function. In backed-up state the handler restores from backup; only in promoted state is rm permitted. Invariant pinned by 4 unit tests.
2. Published package.json no longer carries dev-only scripts.
npm view skills4sh@0.4.2 scripts previously exposed test, check:drift, check:guardskills, check:pack, check:release, setup:hooks, prepublishOnly — every one referencing files (bin/*-check.mjs, tests/, .github/scripts/) that aren't in the published tarball. Consumers running them would hit "module not found." New behavior: bin/clean-package-for-publish.mjs strips them at prepack time and restores at postpack. bin/pack-check.mjs asserts the published package.json has no scripts — a misconfigured hook can no longer silently ship a dirty manifest.
3. branch-protection-drift.yml fails hard on missing secret (except Dependabot).
Once this workflow became a required-status-check (post-v0.4.2), the previous skip-on-no-secret behavior turned into a bypass vector: anyone with permission to delete repo secrets could disable the drift control while CI stayed green. New behavior: hard fail when BRANCH_PROTECTION_TOKEN is absent. The one carve-out is Dependabot-triggered runs (Dependabot has a separate secret store and can't see regular repo secrets) — those skip cleanly so action-upgrade PRs aren't blocked. Verified live: the workflow now successfully diffs live-vs-snapshot on every dispatch with the secret present.
Tests
+12 (101 → 113):
- 4 tests for
interruptCleanupPlanpinning the audit invariant - 8 tests for
clean-package-for-publish.mjscovering the full prepack/postpack cycle, stale-.bakrecovery, stderr isolation, and error handling
Verify
npm install skills4sh@0.4.3
npm audit signatures skills4sh@0.4.3
npm view skills4sh@0.4.3 scripts # should be empty / absent (no dev-script pollution)Full changelog: v0.4.2...v0.4.3
v0.4.2 — tooling-hardening pack
What's new — tooling hardening pack
Closes the "test the verifiers" gap surfaced by the fresh-eyes audit. Adds robustness around installer failure modes. Zero new npm dependencies. Zero public CLI surface change beyond the new npm test script.
Added
bin/lib/parsers.mjs— pure helpers (parsers, semver compare, schema validators) extracted fromdrift-check.mjsandguardskills-check.mjsso they're importable in isolation. Includes a newvalidateSecurityManifest()that does hand-rolled schema validation on.security/<name>.yaml(required blocks present, enum constraints onrisk_tier/network/filesystem/shell/hash_algorithm,SKILL.mdmandatory inintegrity.files).- 42 unit tests in
tests/parsers.test.mjscovering every helper. Total test count is now 101 (up from 59). npm testscript —node --test tests/*.test.mjs. Closes a doc/code drift whereCONTRIBUTING.mdreferencednpm testbut the script didn't exist.- Schema validation runs as part of
npm run check:drift— any.security/*.yamlmissing a required block or carrying an invalid enum value now fails CI loudly instead of being silently accepted.
Installer robustness
- Staging directory uses
crypto.randomBytes(6)instead ofprocess.pid. Two concurrent invocations on the same destination (rare but possible in containers / re-execed wrappers) no longer collide. - SIGINT/SIGTERM cleanup handler registered around each
installSkillcall. An interrupt mid-download no longer leaks.tmp-*directories. - Rollback-safe install — the old skill is renamed aside to
.backup-<random>before the new content moves in, then removed on success. On any failure the backup is restored automatically; if restore also fails, both paths are surfaced in the error so manual recovery is possible.
Branch-protection hardening
required_signatures: trueis now enforced onrefs/heads/main. Every commit pushed to main must be GitHub-verified (SSH or GPG). Complements the existing release-time check inbin/release-check.mjs..github/workflows/branch-protection-drift.ymlnow runs on every PR (no path filter), so it can serve as a required branch-protection context.
Pending one-click action
The sandbox blocked one API call. Please add assert protection matches .github/branch-protection.expected.json to the required-status-checks list at:
Settings → Branches → Edit rule for
main→ Require status checks to pass before merging
The check itself already runs on every PR after this release; only the required gate needs to be toggled in the UI.
Verify
npm install skills4sh@0.4.2
npm audit signatures skills4sh@0.4.2Full changelog: v0.4.1...v0.4.2
v0.4.1 — release-commit signature verification + signing-chain docs
What's new
Closes the last Tier-1 audit item with substantive technical work (lock-file integrity) and brings in routine action-version maintenance.
Security: release-commit signature verification
bin/release-check.mjs now verifies that the release commit itself is GitHub-verified, in addition to the existing tag-signature check. Tag and commit signatures are independent in git (tag.gpgsign and commit.gpgsign are separate config keys). The tag signature attests "the maintainer created this release"; the commit signature attests "the maintainer authored the source code shipped at this release." Both must hold to close the lock-file integrity loop.
Docs: full consumer-verifiable release-integrity chain
SECURITY.md AST02 gains a new subsection enumerating the four-link chain anyone downstream can verify:
- Signed commit —
commit.verification.verified === truevia GitHub API - Signed tag pointing at that commit —
bin/release-check.mjs - SLSA v1 tarball provenance produced by OIDC Trusted Publisher —
npm audit signatures - Registry-side
gitHeadmatches the published commit SHA —bin/verify-published.mjs
skills-lock.json is not separately signed; it's covered transitively by the commit signature plus the provenance attestation. The audit's flagged concern is addressed via the existing signing chain rather than a new signing mechanism — the architectural problem is single-maintainer governance, which separate signing keys don't actually solve.
Maintenance: GitHub Actions bumped (Dependabot PR #1)
| Action | Was | Now |
|---|---|---|
actions/checkout |
v4.3.1 | v6.0.2 |
actions/setup-node |
v4.4.0 | v6.4.0 |
actions/setup-python |
v5.6.0 | v6.2.0 |
github/codeql-action |
v3 | v4.35.4 |
actions/dependency-review-action |
v4.9.0 | v5.0.0 |
All required CI checks passed on the rebased PR before merge.
Verify
npm install skills4sh@0.4.1
npm audit signatures skills4sh@0.4.1
# Optionally inspect the full chain:
npm view skills4sh@0.4.1 gitHead # commit SHA recorded by registry
gh api repos/t4sh/skills4sh/commits/$(npm view skills4sh@0.4.1 gitHead --json | jq -r .) \
--jq '.commit.verification.verified' # → trueFull changelog: v0.4.0...v0.4.1
v0.4.0 — add skills4sh remove uninstall command
What's new — minor bump
First minor release since v0.1. Adds a long-overdue uninstall command.
Added: skills4sh remove
Completes the installer lifecycle. Previously, removing a skill meant rm -rf ~/.claude/skills/<name> manually — now it's a first-class subcommand with safety rails.
npx skills4sh remove agent-memory # uninstall one skill
npx skills4sh remove --all # uninstall every installed skill
npx skills4sh remove agent-memory --dry-run # print what would be deleted, no delete
npx skills4sh remove foo --dest ~/.cursor/skills # custom destinationSafety: only directories under --dest that contain a SKILL.md are eligible for removal. Sibling files, unrelated directories, and anything without a SKILL.md are left untouched. Refuses destructive ops on misconfigured paths.
Offline: pure-local — never makes a GitHub fetch and never needs GITHUB_TOKEN. The uninstall path bails before any network call.
Tier alignment
Closes the last Tier-2 audit item (#6 — uninstall command). Remaining open audit items:
- Tier 2 #7 (opt-in hook documentation): already addressed structurally in CONTRIBUTING.md (v0.3.8). Not a code change.
- Tier 3 #8 (skill execution untested): inherent to markdown-skill products. Mitigated by PR template + human review.
Verify
npm install skills4sh@0.4.0
npm audit signatures skills4sh@0.4.0
skills4sh --help # see the new remove sectionFull changelog: v0.3.11...v0.4.0
v0.3.11 — discord-harvest 1.7.1 untrusted-content advisory
What's new
Documentation-only release closing a third-party audit recommendation. No code change to the installer or to discord-harvest's actual behavior.
discord-harvest 1.7.0 → 1.7.1 — Trust Boundary advisory
Added a top-line Trust Boundary — Read Before Running section to discord-harvest/SKILL.md, placed immediately after Installation and before "What I Can Help With". The skill always handled untrusted content correctly, but the SKILL.md didn't surface that fact prominently — a user invoking the skill might not have realized they were archiving content from arbitrary, sometimes-adversarial Discord users.
The new advisory enumerates the defenses already in place:
- Fixed operation set. Never interprets message content as instructions, tool calls, or commands.
- Social-engineering filename detection.
flag_suspicious()surfaces names likeoverride-claude.exeorsystem-prompt.txtin the pre-download staging summary. - No message text persisted. Manifests record filenames, redacted URLs, and embed metadata only — narrows the prompt-injection surface for any LLM that later reads the saved files.
- CDN allowlist on downloads.
validate_urlrestrictscurlto Discord's own CDN hosts. Third-party links are recorded inlinks.mdandmanifest.jsonbut never fetched — defeats SSRF and malicious-redirect risk. - Path-traversal sanitization.
sanitize_filenameneutralizes filenames like../../.envbefore any disk write.
Verify
npm install skills4sh@0.3.11
npm audit signatures skills4sh@0.3.11Full changelog: v0.3.10...v0.3.11
v0.3.10 — SHA-pin GitHub Actions, Dependabot, branch-protection drift
What's new
CI security hardening: GitHub Actions are now SHA-pinned, Dependabot keeps them moving, and branch-protection drift detection is plumbed (opt-in via PAT).
Zero impact on installed skills or the installer CLI — every change is in .github/.
Security
- All GitHub Actions SHA-pinned. Every
uses:directive across 6 workflow files now references a commit SHA with a# vX.Y.Zhuman-readable comment. Floating major tags (@v4) reflect whatever upstream pushes to that ref — an upstream account compromise would land on every CI run on the next refresh. SHA pinning freezes that surface; Dependabot moves it forward under review. - Dependabot for
github-actions. Weekly grouped PRs, batched so a single security gate covers the batch. Each PR hits the full required-checks matrix before merge. PR #1 (auto-generated, already passing all checks) is the first proof. - Branch-protection drift detection (opt-in).
.github/workflows/branch-protection-drift.ymlplus checked-in snapshot.github/branch-protection.expected.jsoncapture the intendedrefs/heads/mainprotection state (14 required checks, strict mode, linear history, no force-push, no deletion, conversation resolution required). The workflow is inert by default — the defaultGITHUB_TOKENdoesn't supportAdministration: read. Setting aBRANCH_PROTECTION_TOKENrepo secret (fine-grained PAT scoped to this repo,Administration: Read-only) enables the daily drift assertion. Without the secret it logs setup instructions and exits cleanly.
Maintainer setup (optional)
To enable the drift check:
- https://github.com/settings/personal-access-tokens/new
- Resource owner: your account; Repository access: only
t4sh/skills4sh - Permissions → Repository → Administration: Read-only
- Save as repo secret
BRANCH_PROTECTION_TOKEN
The check then runs daily at 06:15 UTC. The snapshot is the documented intent regardless of whether the check is enabled.
Verify
npm install skills4sh@0.3.10
npm audit signatures skills4sh@0.3.10 # SLSA v1 provenance via OIDC Trusted PublisherFull changelog: v0.3.9...v0.3.10