1package agent
2
3import (
4 "archive/tar"
5 "bytes"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "github.com/patriceckhart/zot/packages/agent/tools"
12)
13
14func writeTestZotfile(t *testing.T, manifest string) string {
15 t.Helper()
16 dir := t.TempDir()
17 if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(manifest), 0o600); err != nil {
18 t.Fatal(err)
19 }
20 if err := os.WriteFile(filepath.Join(dir, "AGENT.md"), []byte("Be useful."), 0o600); err != nil {
21 t.Fatal(err)
22 }
23 return dir
24}
25
26func TestLoadZotfileRejectsUnenforcedPermissions(t *testing.T) {
27 for _, field := range []string{
28 `"net":{"allow":["example.com"]}`,
29 `"env":{"read":["HOME"]}`,
30 } {
31 dir := writeTestZotfile(t, `{"zotfile":1,"name":"test","permissions":{`+field+`}}`)
32 if _, _, err := loadZotfile(dir); err == nil {
33 t.Fatalf("manifest with %s was accepted", field)
34 }
35 }
36}
37
38func TestLoadZotfileRejectsUnsafeOrCollidingNames(t *testing.T) {
39 for _, name := range []string{"...", "Name", "two words", "a/b"} {
40 dir := writeTestZotfile(t, `{"zotfile":1,"name":"`+name+`"}`)
41 if _, _, err := loadZotfile(dir); err == nil {
42 t.Fatalf("unsafe manifest name %q was accepted", name)
43 }
44 }
45}
46
47func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) {
48 dir := writeTestZotfile(t, `{"zotfile":1,"name":"test"}`)
49 ext := filepath.Join(dir, "extensions", "bad")
50 if err := os.MkdirAll(ext, 0o700); err != nil {
51 t.Fatal(err)
52 }
53 if err := os.WriteFile(filepath.Join(ext, "extension.json"), []byte(`{"name":"bad"}`), 0o600); err != nil {
54 t.Fatal(err)
55 }
56 if _, _, err := loadZotfile(dir); err == nil || !strings.Contains(err.Error(), "cannot yet be confined") {
57 t.Fatalf("unexpected error: %v", err)
58 }
59}
60
61func TestCheckZotfileMinVersion(t *testing.T) {
62 var zf zotfileLoaded
63 zf.Manifest.Runtime.MinZot = "0.3.0"
64 if err := checkZotfileRequirements(zf, "0.2.75"); err == nil {
65 t.Fatal("old zot version accepted")
66 }
67 if err := checkZotfileRequirements(zf, "0.3.0"); err != nil {
68 t.Fatalf("minimum version rejected: %v", err)
69 }
70}
71
72func TestApplyZotfileModelRequirementsRejectsUnsupportedFields(t *testing.T) {
73 var m ZotfileManifest
74 m.Model.MinTier = "frontier"
75 if err := applyZotfileModelRequirements(&Args{}, m); err == nil {
76 t.Fatal("unsupported min_tier was ignored")
77 }
78 m.Model.MinTier = ""
79 m.Model.Requires = []string{"audio"}
80 if err := applyZotfileModelRequirements(&Args{}, m); err == nil {
81 t.Fatal("unsupported capability was ignored")
82 }
83}
84
85func TestUntarRejectsTraversalAndOversizedEntry(t *testing.T) {
86 makeTar := func(name string, size int64) []byte {
87 var buf bytes.Buffer
88 tw := tar.NewWriter(&buf)
89 if err := tw.WriteHeader(&tar.Header{Name: name, Mode: 0o600, Size: size}); err != nil {
90 t.Fatal(err)
91 }
92 _ = tw.Close()
93 return buf.Bytes()
94 }
95 if err := untar(bytes.NewReader(makeTar("../escape", 0)), t.TempDir()); err == nil {
96 t.Fatal("path traversal accepted")
97 }
98 if err := untar(bytes.NewReader(makeTar("large", maxZotfileEntrySize+1)), t.TempDir()); err == nil {
99 t.Fatal("oversized entry accepted")
100 }
101}
102
103func TestPermissionSummaryShowsDeniedScopes(t *testing.T) {
104 got := permissionSummary(tools.PermissionSet{})
105 if !strings.Contains(got, "fs read: none") || !strings.Contains(got, "fs write: none") {
106 t.Fatalf("summary did not show denied scopes:\n%s", got)
107 }
108}