10 "github.com/stretchr/testify/require"
13 func TestBackupArchiveRestoresBytesAndModes(t *testing.T) {
15 require.NoError(t, os.Mkdir(filepath.Join(root, "agent"), 0o750))
16 require.NoError(t, os.WriteFile(filepath.Join(root, "agent", "instructions.md"), []byte("Keep the whole chat.\n"), 0o640))
18 archive := filepath.Join(t.TempDir(), "backup.tar.gz")
19 size, err := backupArchive(t.Context(), root, archive)
20 require.NoError(t, err)
22 info, err := os.Stat(archive)
23 require.NoError(t, err)
24 require.Equal(t, size, info.Size())
25 require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
27 restored := t.TempDir()
28 require.NoError(t, backupExtract(t.Context(), archive, restored))
30 data, err := os.ReadFile(filepath.Join(restored, "agent", "instructions.md"))
31 require.NoError(t, err)
32 require.Equal(t, "Keep the whole chat.\n", string(data))
34 info, err = os.Stat(filepath.Join(restored, "agent"))
35 require.NoError(t, err)
36 require.Equal(t, os.FileMode(0o750), info.Mode().Perm())
38 info, err = os.Stat(filepath.Join(restored, "agent", "instructions.md"))
39 require.NoError(t, err)
40 require.Equal(t, os.FileMode(0o640), info.Mode().Perm())
43 func TestRestoreRejectsUnsafeArchiveEntries(t *testing.T) {
44 testCases := []struct {
49 name: "absolute paths fail",
52 Typeflag: tar.TypeReg,
57 name: "parent paths fail",
60 Typeflag: tar.TypeReg,
65 name: "embedded parent paths fail",
67 Name: "agent/../../escape",
68 Typeflag: tar.TypeReg,
73 name: "symbolic links fail",
76 Typeflag: tar.TypeSymlink,
77 Linkname: "../escape",
82 name: "hard links fail",
85 Typeflag: tar.TypeLink,
91 name: "device files fail",
94 Typeflag: tar.TypeChar,
99 name: "setuid modes fail",
102 Typeflag: tar.TypeReg,
107 name: "lock replacement fails",
110 Typeflag: tar.TypeReg,
115 for _, tc := range testCases {
116 t.Run(tc.name, func(t *testing.T) {
117 archive := backupTestArchive(t, tc.header)
118 require.Error(t, backupExtract(t.Context(), archive, t.TempDir()))
123 func TestBackupFailureKeepsTheDestination(t *testing.T) {
125 require.NoError(t, os.Symlink("missing", filepath.Join(root, "link")))
127 archive := filepath.Join(t.TempDir(), "backup.tar.gz")
128 require.NoError(t, os.WriteFile(archive, []byte("keep"), 0o600))
130 _, err := backupArchive(t.Context(), root, archive)
131 require.ErrorContains(t, err, "unsupported backup entry")
133 data, err := os.ReadFile(archive)
134 require.NoError(t, err)
135 require.Equal(t, "keep", string(data))
137 entries, err := os.ReadDir(filepath.Dir(archive))
138 require.NoError(t, err)
139 require.Len(t, entries, 1)
142 func backupTestArchive(t *testing.T, headers ...tar.Header) string {
145 path := filepath.Join(t.TempDir(), "input.tar.gz")
146 file, err := os.Create(path)
147 require.NoError(t, err)
149 compressed := gzip.NewWriter(file)
151 archive := tar.NewWriter(compressed)
152 for _, header := range headers {
153 require.NoError(t, archive.WriteHeader(&header))
156 require.NoError(t, archive.Close())
157 require.NoError(t, compressed.Close())
158 require.NoError(t, file.Close())