package exec import ( "errors" "os" "path/filepath" "testing" ) func newFileSandbox(t *testing.T, roots ...string) *FileSandbox { t.Helper() fs, err := NewFileSandbox(roots, discardLogger()) if err != nil { t.Fatalf("NewFileSandbox: %v", err) } return fs } func TestFileSandbox_Resolve_Allowed(t *testing.T) { t.Parallel() root := t.TempDir() realRoot := root if r, err := filepath.EvalSymlinks(root); err == nil { realRoot = r } fs := newFileSandbox(t, root) got, err := fs.Resolve(filepath.Join(root, "a.txt")) if err != nil { t.Fatalf("Resolve: %v", err) } if want := filepath.Join(realRoot, "a.txt"); got != want { t.Errorf("Resolve = %q, want %q", got, want) } } func TestFileSandbox_Resolve_OutsideRejected(t *testing.T) { t.Parallel() fs := newFileSandbox(t, t.TempDir()) outside := filepath.Join(os.TempDir(), "cyrene-outside-file.txt") _, err := fs.Resolve(outside) if !errors.Is(err, ErrPathOutsideRoot) { t.Fatalf("err = %v, want ErrPathOutsideRoot", err) } } func TestFileSandbox_Resolve_SymlinkEscape(t *testing.T) { t.Parallel() root := t.TempDir() outside := t.TempDir() link := filepath.Join(root, "escape") if err := os.Symlink(outside, link); err != nil { t.Skipf("symlink not supported: %v", err) } fs := newFileSandbox(t, root) _, err := fs.Resolve(filepath.Join(link, "secret.txt")) if !errors.Is(err, ErrSymlinkEscape) { t.Fatalf("err = %v, want ErrSymlinkEscape", err) } } func TestFileSandbox_WriteReadList(t *testing.T) { t.Parallel() fs := newFileSandbox(t, t.TempDir()) if err := fs.WriteFile("a.txt", []byte("content"), 0o600); err != nil { t.Fatalf("WriteFile: %v", err) } data, err := fs.ReadFile("a.txt") if err != nil { t.Fatalf("ReadFile: %v", err) } if string(data) != "content" { t.Errorf("ReadFile = %q, want content", data) } entries, err := fs.List(".") if err != nil { t.Fatalf("List: %v", err) } if len(entries) != 1 { t.Errorf("len(entries) = %d, want 1", len(entries)) } } func TestFileSandbox_NoRoot(t *testing.T) { t.Parallel() fs := newFileSandbox(t, "") _, err := fs.Resolve("a.txt") if !errors.Is(err, ErrNoAllowedRoot) { t.Fatalf("err = %v, want ErrNoAllowedRoot", err) } }