Skip to content

Commit f4576c7

Browse files
committed
fix(trace): reject symlinked tracked paths
Validate every fallback path component before opening tracked files so symlinks and special files stay outside worktree snapshots. Assisted-by: Vibhav's Pi Signed-off-by: Vibhav Bobade <vibhav.bobde@gmail.com>
1 parent 9ff2c99 commit f4576c7

2 files changed

Lines changed: 38 additions & 7 deletions

File tree

‎app/cli/internal/trace/git/gogit.go‎

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,8 @@ func snapshotWorktree(repoRoot string) (map[string]string, error) {
332332
if _, ok := sig[path]; ok {
333333
continue
334334
}
335-
f, openErr := root.Open(filepath.FromSlash(path))
336-
if openErr != nil {
337-
continue
338-
}
339-
info, statErr := f.Stat()
340-
if statErr != nil || !info.Mode().IsRegular() {
341-
_ = f.Close()
335+
f, ok := openRegularRootFile(root, path)
336+
if !ok {
342337
continue
343338
}
344339
hash, hashErr := hashFileReader(f)
@@ -375,6 +370,28 @@ func trackedPaths(repoRoot string) map[string]bool {
375370
return tracked
376371
}
377372

373+
func openRegularRootFile(root *os.Root, path string) (*os.File, bool) {
374+
parts := strings.Split(filepath.ToSlash(path), "/")
375+
current := ""
376+
for i, part := range parts {
377+
current = filepath.Join(current, filepath.FromSlash(part))
378+
info, err := root.Lstat(current)
379+
if err != nil || info.Mode()&os.ModeSymlink != 0 {
380+
return nil, false
381+
}
382+
if i < len(parts)-1 {
383+
if !info.IsDir() {
384+
return nil, false
385+
}
386+
} else if !info.Mode().IsRegular() {
387+
return nil, false
388+
}
389+
}
390+
391+
f, err := root.Open(filepath.FromSlash(path))
392+
return f, err == nil
393+
}
394+
378395
// hashFileContent returns the hex-encoded SHA-256 of the file's content.
379396
func hashFileContent(path string) (string, error) {
380397
f, err := os.Open(path)

‎app/cli/internal/trace/git/worktree_index_test.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,20 @@ func TestGoGitClient_SnapshotWorktree_DoesNotFollowEscapingSymlink(t *testing.T)
134134
require.NotContains(t, snapshot, "link/outside.txt")
135135
}
136136

137+
func TestGoGitClient_SnapshotWorktree_DoesNotFollowInRootSymlink(t *testing.T) {
138+
dir := t.TempDir()
139+
runIndexGit(t, dir, "init", "-q")
140+
require.NoError(t, os.MkdirAll(filepath.Join(dir, "target"), 0o700))
141+
require.NoError(t, os.WriteFile(filepath.Join(dir, "target", "file.txt"), []byte("inside\n"), 0o600))
142+
require.NoError(t, os.Symlink("target", filepath.Join(dir, "link")))
143+
blob := writeGitObject(t, dir, "inside\n")
144+
writeGitIndex(t, dir, fmt.Sprintf("100644 %s 0\tlink/file.txt\n", blob))
145+
146+
snapshot, err := NewGoGitClient().SnapshotWorktree(dir)
147+
require.NoError(t, err)
148+
require.NotContains(t, snapshot, "link/file.txt")
149+
}
150+
137151
func TestGoGitClient_UnstagedFiles_ChangedFilterMarksRawEqualityDirty(t *testing.T) {
138152
dir := t.TempDir()
139153
runIndexGit(t, dir, "init", "-q")

0 commit comments

Comments
 (0)