package skillpack import ( "os" "path/filepath" "testing" ) func sampleTree() Tree { return Tree{ ManifestName: []byte(goodManifest), "scripts/fill.py": []byte("print('hi')\n"), "references/spec.md": []byte("# spec\n"), } } func TestTreeDigest_StableAndContentSensitive(t *testing.T) { a := sampleTree() b := sampleTree() if a.Digest() != b.Digest() { t.Fatal("identical trees must share a digest") } b["scripts/fill.py"] = []byte("print('bye')\n") if a.Digest() == b.Digest() { t.Fatal("content change must change the digest") } // Adding a file changes the digest. c := sampleTree() c["extra.txt"] = []byte("x") if a.Digest() == c.Digest() { t.Fatal("added file must change the digest") } } func TestLoadPack(t *testing.T) { p, err := LoadPack(sampleTree()) if err != nil { t.Fatal(err) } if p.Manifest.Name != "pdf-processing" { t.Errorf("name = %q", p.Manifest.Name) } if len(p.Bundled) != 2 || p.Bundled[0] != "references/spec.md" || p.Bundled[1] != "scripts/fill.py" { t.Errorf("bundled = %v (want sorted, sans SKILL.md)", p.Bundled) } if p.Digest == "" { t.Error("digest empty") } } func TestLoadPack_NoManifest(t *testing.T) { if _, err := LoadPack(Tree{"readme.md": []byte("x")}); err != ErrNoManifest { t.Fatalf("want ErrNoManifest, got %v", err) } } func TestTreeWriteTo(t *testing.T) { dir := t.TempDir() if err := sampleTree().WriteTo(dir); err != nil { t.Fatal(err) } got, err := os.ReadFile(filepath.Join(dir, "scripts", "fill.py")) if err != nil { t.Fatal(err) } if string(got) != "print('hi')\n" { t.Errorf("staged content = %q", got) } } 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")} if err := evil.WriteTo(dir); err == nil { t.Fatal("expected traversal rejection") } if _, err := os.Stat(filepath.Join(filepath.Dir(dir), "escape.txt")); err == nil { t.Fatal("traversal file was written outside dir") } }