返回 DeepSeek-Reasonix
worktree_add_hardening_test.go
根目录 / internal / worktree / worktree_add_hardening_test.go
1 package worktree
2
3 import (
4 "context"
5 "os"
6 "os/exec"
7 "path/filepath"
8 "runtime"
9 "strings"
10 "testing"
11 )
12
13 // Create checks the new worktree out. That checkout runs in a child whose
14 // gitdir is the new worktree, so a conditional include keyed on that gitdir or
15 // on the new branch is invisible to a driver listing done in the source
16 // repository — the child would still run the filter. Create must populate the
17 // worktree without running any such driver, and the file must arrive intact.
18 func TestCreateWorktreeDoesNotRunConditionallyIncludedDrivers(t *testing.T) {
19 if runtime.GOOS == "windows" {
20 t.Skip("payload script is POSIX shell")
21 }
22 requireGit(t)
23 for _, cond := range []string{"gitdir:**/worktrees/**", "onbranch:reasonix/**"} {
24 t.Run(cond, func(t *testing.T) {
25 repo := t.TempDir()
26 git := func(args ...string) {
27 t.Helper()
28 cmd := exec.Command("git", append([]string{"-C", repo}, args...)...)
29 cmd.Env = append(os.Environ(),
30 "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@t", "GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@t")
31 if out, err := cmd.CombinedOutput(); err != nil {
32 t.Fatalf("git %v: %v\n%s", args, err, out)
33 }
34 }
35 git("init")
36 if err := os.WriteFile(filepath.Join(repo, ".gitattributes"), []byte("f.txt filter=pwn\n"), 0o644); err != nil {
37 t.Fatal(err)
38 }
39 if err := os.WriteFile(filepath.Join(repo, "f.txt"), []byte("hello\n"), 0o644); err != nil {
40 t.Fatal(err)
41 }
42 git("add", "-A")
43 git("commit", "-m", "initial")
44
45 marker := filepath.Join(t.TempDir(), "executed")
46 payload := filepath.Join(t.TempDir(), "payload.sh")
47 if err := os.WriteFile(payload, []byte("#!/bin/sh\necho ran >> '"+marker+"'\ncat\n"), 0o755); err != nil {
48 t.Fatal(err)
49 }
50 evil := filepath.Join(t.TempDir(), "evil.cfg")
51 if err := os.WriteFile(evil, []byte("[filter \"pwn\"]\n\tsmudge = "+payload+"\n\tclean = "+payload+"\n"), 0o644); err != nil {
52 t.Fatal(err)
53 }
54 cfg, err := os.OpenFile(filepath.Join(repo, ".git", "config"), os.O_APPEND|os.O_WRONLY, 0)
55 if err != nil {
56 t.Fatal(err)
57 }
58 if _, err := cfg.WriteString("[includeIf \"" + cond + "\"]\n\tpath = " + evil + "\n"); err != nil {
59 t.Fatal(err)
60 }
61 _ = cfg.Close()
62
63 result, err := Create(context.Background(), opened(t, repo), t.TempDir())
64 if err != nil {
65 t.Fatalf("Create: %v", err)
66 }
67 data, err := os.ReadFile(filepath.Join(result.WorktreeRoot, "f.txt"))
68 if err != nil || string(data) != "hello\n" {
69 t.Fatalf("worktree f.txt = %q, %v; want the committed bytes", data, err)
70 }
71 if _, err := os.Stat(marker); err == nil {
72 t.Fatal("creating a worktree ran a conditionally-included driver")
73 } else if !os.IsNotExist(err) {
74 t.Fatalf("stat marker: %v", err)
75 }
76 if strings.HasPrefix(cond, "onbranch") && !strings.HasPrefix(result.Branch, "reasonix/") {
77 t.Fatalf("branch = %q, want the onbranch condition to have matched", result.Branch)
78 }
79 })
80 }
81 }
82
83 // RollbackCreate removes an unused worktree. A bare `worktree remove` runs
84 // status inside the linked worktree, whose config.worktree the source-root
85 // listing never saw; a driver there would run. Removal must not run it.
86 func TestRollbackCreateDoesNotRunLinkedWorktreeDrivers(t *testing.T) {
87 if runtime.GOOS == "windows" {
88 t.Skip("payload script is POSIX shell")
89 }
90 requireGit(t)
91 repo := initRepo(t)
92 gitTest(t, repo, "config", "user.name", "t")
93 gitTest(t, repo, "config", "user.email", "t@t")
94 if err := os.WriteFile(filepath.Join(repo, ".gitattributes"), []byte("README.md filter=pwn\n"), 0o644); err != nil {
95 t.Fatal(err)
96 }
97 gitTest(t, repo, "add", ".gitattributes")
98 gitTest(t, repo, "commit", "-m", "attrs")
99 result, err := Create(context.Background(), opened(t, repo), t.TempDir())
100 if err != nil {
101 t.Fatalf("Create: %v", err)
102 }
103 gitDir := strings.TrimSpace(gitTest(t, result.WorktreeRoot, "rev-parse", "--absolute-git-dir"))
104 gitTest(t, result.WorktreeRoot, "config", "extensions.worktreeConfig", "true")
105 marker := filepath.Join(t.TempDir(), "executed")
106 payload := filepath.Join(t.TempDir(), "payload.sh")
107 if err := os.WriteFile(payload, []byte("#!/bin/sh\necho ran >> '"+marker+"'\ncat\n"), 0o755); err != nil {
108 t.Fatal(err)
109 }
110 if err := os.WriteFile(filepath.Join(gitDir, "config.worktree"),
111 []byte("[filter \"pwn\"]\n\tclean = "+payload+"\n\tsmudge = "+payload+"\n"), 0o644); err != nil {
112 t.Fatal(err)
113 }
114
115 if err := RollbackCreate(context.Background(), result); err != nil {
116 t.Fatalf("RollbackCreate: %v", err)
117 }
118 if _, err := os.Stat(result.WorktreeRoot); !os.IsNotExist(err) {
119 t.Fatalf("worktree still present after rollback: %v", err)
120 }
121 if _, err := os.Stat(marker); err == nil {
122 t.Fatal("removing the worktree ran a linked-worktree driver")
123 }
124 }
125
126 // When populating the new worktree fails, Create leaves neither the worktree
127 // nor the branch its add created behind.
128 func TestCreateCleansUpBranchWhenPopulateFails(t *testing.T) {
129 if runtime.GOOS == "windows" {
130 t.Skip("POSIX false binary")
131 }
132 if _, err := exec.LookPath("git"); err != nil {
133 t.Skip("git not installed")
134 }
135 repo := t.TempDir()
136 git := func(args ...string) string {
137 t.Helper()
138 cmd := exec.Command("git", append([]string{"-C", repo}, args...)...)
139 cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=t", "GIT_AUTHOR_EMAIL=t@t", "GIT_COMMITTER_NAME=t", "GIT_COMMITTER_EMAIL=t@t")
140 out, err := cmd.CombinedOutput()
141 if err != nil {
142 t.Fatalf("git %v: %v\n%s", args, err, out)
143 }
144 return string(out)
145 }
146 git("init", "-q")
147 if err := os.WriteFile(filepath.Join(repo, ".gitattributes"), []byte("f.txt filter=req\n"), 0o644); err != nil {
148 t.Fatal(err)
149 }
150 if err := os.WriteFile(filepath.Join(repo, "f.txt"), []byte("hello\n"), 0o644); err != nil {
151 t.Fatal(err)
152 }
153 git("add", "-A")
154 git("commit", "-q", "-m", "init")
155 // A user-scope required filter whose smudge fails: user scope stays live,
156 // so the checkout inside the new worktree fails.
157 global := filepath.Join(t.TempDir(), "gitconfig")
158 if err := os.WriteFile(global, []byte("[filter \"req\"]\n\tclean = cat\n\tsmudge = false\n\trequired = true\n"), 0o644); err != nil {
159 t.Fatal(err)
160 }
161 t.Setenv("GIT_CONFIG_GLOBAL", global)
162
163 if _, err := Create(context.Background(), opened(t, repo), t.TempDir()); err == nil {
164 t.Fatal("Create succeeded with a failing required filter, want an error")
165 }
166 if refs := strings.TrimSpace(git("for-each-ref", "--format=%(refname)", "refs/heads/reasonix/")); refs != "" {
167 t.Fatalf("branches left behind: %s", refs)
168 }
169 if list := git("worktree", "list", "--porcelain"); strings.Count(list, "worktree ") != 1 {
170 t.Fatalf("worktrees left behind:\n%s", list)
171 }
172 }
173
173 lines GO