fix(skillpack): address review — symlink read, git arg-injection, dup-subscribe, nil panics
executus CI / test (pull_request) Successful in 3m30s
executus CI / test (pull_request) Successful in 3m30s
Real issues from the PR review: - security: readTree now skips symlinks (a pack with SKILL.md -> /etc/passwd or scripts/x -> ../../.ssh/id_rsa could read host files); covers file and dir symlinks, incl. within a git subpath - security: GitSource rejects url/ref beginning with '-' (git arg injection) and clones with '--' separator; --filter=blob:none (blobless partial clone) instead of full-history clone - correctness: Subscribe no longer swallows a non-ErrNotFound store error from GetByName (would create a duplicate subscription); handles *GitSource as well as GitSource in the URL/subpath extraction - correctness: pinTo no longer renames a subscription, so Apply can't silently collide two subscriptions when an upstream pack changes its name - validation: isKebab rejects leading/trailing/consecutive hyphens; BOM- prefixed SKILL.md now parses (matches the doc comment) - robustness: Catalog/Activate/renderPackBody/Stage guard nil/malformed packs - test cleanup: Syncer.Store field renamed Cache (collided with the Store interface); test NewID returns distinct ids - tests: symlink-skip, BOM, strict-kebab, nil-pack-safety Deferred (advisory perf, documented): PackCache stores raw trees so activation re-parses; CheckAll is serial. Both fine at expected scale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,31 @@ func TestTreeWriteTo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadTree_SkipsSymlinks(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, ManifestName), []byte(goodManifest), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A malicious pack pointing at a host file must NOT be read into the tree.
|
||||
secret := filepath.Join(t.TempDir(), "secret")
|
||||
if err := os.WriteFile(secret, []byte("TOPSECRET"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(secret, filepath.Join(dir, "leak")); err != nil {
|
||||
t.Skipf("symlink unsupported: %v", err)
|
||||
}
|
||||
tree, err := readTree(os.DirFS(dir))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := tree["leak"]; ok {
|
||||
t.Fatal("symlink was followed into the tree — arbitrary host file read")
|
||||
}
|
||||
if _, ok := tree[ManifestName]; !ok {
|
||||
t.Fatal("real file should still be read")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeWriteTo_RejectsTraversal(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
evil := Tree{"../escape.txt": []byte("nope")}
|
||||
|
||||
Reference in New Issue
Block a user