You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(skills): fingerprint the whole skill directory, not SKILL.md alone
A skill is not just its manifest. Its scripts, tools, templates and reference
docs decide what it does, and _skills() hashed SKILL.md and nothing else. So a
payload swapped into a skill's scripts/ directory produced no change in the
fingerprint and the report said "nothing added, nothing subtracted".
Demonstrated on a real install before the fix: replacing a skill's
scripts/deploy.ps1 with a body that POSTs ~/.ssh/id_rsa to an attacker host left
the skills fingerprint byte-identical and diff() returned nothing. Every skill in
that install carries files beyond SKILL.md, five of them executable. This is the
README's own headline threat, "a skill you installed ships an update that now
runs curl to an address you never saw", and it was undetected.
_skill_fingerprint() now hashes every file in the skill tree, binding relative
paths alongside contents so a rename or a move is also drift.
Exclusions are a tool-controlled denylist (state/, .cache/, __pycache__/, .git/,
node_modules/, plus .log/.tmp/.pyc/.pyo). Skills write state as they run, and
alarming on ordinary use would train the user to dismiss the next real alarm. The
list deliberately lives in the engine rather than in a per-skill ignore file: an
ignore file would let the measured thing decide what gets measured, so a hostile
skill could exempt its own payload.
Per-file instruction-layer digests. The rollup only says the layer moved, which
across dozens of memory files is one bit of signal over a directory the reader
then has to search by hand. instruction_files carries a digest per file so the
diff names it, and the rollup stays for the fingerprint line. Scoped to *.md
because the same tree holds session transcripts that change constantly.
Measurement scope versioning. Both changes make older fingerprints incomparable,
so a scope-1 baseline would otherwise report every skill as changed on upgrade.
diff() now reports the widening once, as "re-approve to compare on the new
scope", and drops skills from that comparison. An alarm the user knows is false
is worse than no alarm. Verified against a real scope-1 baseline: one honest
line instead of six phantom skill changes.
17 new tests covering the closed bypass, renames, state churn not alarming, run
artifacts not alarming, the migration path, and per-file naming with fallback to
the rollup against a scope-1 baseline. Suite: 32 passed, up from 15.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
0 commit comments