Enforce install-dir containment on uninstall; resolve manifest verification key - #35
Merged
Merged
Conversation
…cation key Uninstall walked plugin file names straight from the manifest and passed the joined path to removeOwnedFile, while the install path (reconcile- PluginFiles) rejected any name that resolved outside the plugin install directory. Both sides now share a pathWithin helper, so a name that escapes installDir is reported as a RemovalError instead of being acted on. Manifest signature verification previously required the caller to supply Config.ManifestPublicKey; a nil value silently skipped verification. The key is now resolved from Config, then PILOT_SKILLINJECT_PUBKEY, then ~/.pilot/skillinject.pub, then the compiled-in DefaultManifestPublicKey- Hex. Config.RequireSignedManifest (and PILOT_SKILLINJECT_REQUIRE_SIG) makes a resolved key mandatory. Both default off, so behaviour with no key configured is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two audit findings in
skillinject.L27 (high) — uninstall omitted the containment guard the install path enforces
reconcilePluginFilesrejects anyplugin.files[].namewhose joined path resolves outside the plugin install directory.Uninstalldid not: it joined the same manifest-supplied name and handed the result straight toremoveOwnedFile, so a name like../../../.ssh/authorized_keysdeleted a file the install path had never written.Both sides now share a
pathWithin(dir, path)helper inmanifest.go. Out-of-scope names are reported as aRemovalErrorand skipped. In-scope removals are unchanged.L28 (high) — manifest verification was opt-in and nil-by-default
Config.ManifestPublicKeyhad to be supplied by the caller; nil silently skipped Ed25519 detached-signature verification, leaving TLS as the only check on the fetched manifest and repo files.The key is now resolved in precedence order:
Config.ManifestPublicKeyPILOT_SKILLINJECT_PUBKEY(hex or base64)~/.pilot/skillinject.pubDefaultManifestPublicKeyHexConfig.RequireSignedManifest/PILOT_SKILLINJECT_REQUIRE_SIGturns a resolved key from optional into mandatory.Both new controls default off: with no key configured anywhere, fetch behaviour is byte-identical to before, so nothing breaks for existing installs. Operators can now enable verification without a code change, and populating
DefaultManifestPublicKeyHex(currently"") flips it on by default once a signing key is published.Not addressed here: the "move to org repo" half of L28.
DefaultManifestURL/DefaultRepoBaseURLstill point atTeoSlayer/pilot-skillsbecause that repo has not been transferred yet (itsoriginis stillTeoSlayer/pilot-skills); repointing now would break every install. The existingTODOcomments inmanifest.gostill track it.Tests
zz_uninstall_traversal_test.go— an escaping name leaves a seeded file outside the install dir byte-identical and surfaces aRemovalError; an in-dir file is still deleted; the install side does not write outside either.zz_manifest_key_test.go—pathWithintable, key decoding (hex / base64 / raw base64 / bad input), full resolution precedence, require-flag parsing, andgetOrVerifyin all three modes (no key → unverified and no.sigfetched; require-without-key → error; resolved key → verifies and rejects a tampered body).go build ./...,go vet ./...,go test ./...green.gofmt -lflagsservice.goandzz_skillinject_test.go, both pre-existing and untouched.🤖 Generated with Claude Code