From 26baea3dcd327912b03a3f038b6a26e0201493b8 Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Tue, 14 Jul 2026 22:06:38 +0200 Subject: [PATCH] Merge commit from fork --- taskfile/node_git.go | 9 +++++++-- taskfile/node_git_test.go | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/taskfile/node_git.go b/taskfile/node_git.go index 7f3f2d8b..5b96ee6f 100644 --- a/taskfile/node_git.go +++ b/taskfile/node_git.go @@ -231,7 +231,11 @@ func (node *GitNode) CacheKey() string { // Unlike CacheKey() which includes the file path, this identifies the repository itself. // Two GitNodes with the same repo+ref but different file paths will share the same cache. // -// Returns a path like: github.com/user/repo.git/main +// The identity is hashed into a single, filesystem-safe path segment. This prevents an +// attacker-controlled ref (e.g. "../../victim") from being used as a raw path component, +// which would otherwise let the cache directory escape the task-git-repos root (CWE-22). +// +// Returns a path like: git/ func (node *GitNode) repoCacheKey() string { repoPath := strings.Trim(node.url.Path, "/") @@ -240,7 +244,8 @@ func (node *GitNode) repoCacheKey() string { ref = "_default_" // Placeholder for the remote's default branch } - return filepath.Join(node.url.Host, repoPath, ref) + identity := strings.Join([]string{node.url.Host, repoPath, ref}, "/") + return filepath.Join("git", checksum([]byte(identity))) } func splitURLOnDoubleSlash(u *url.URL) (string, string) { diff --git a/taskfile/node_git_test.go b/taskfile/node_git_test.go index 71ea0465..0c65cd65 100644 --- a/taskfile/node_git_test.go +++ b/taskfile/node_git_test.go @@ -1,6 +1,9 @@ package taskfile import ( + "os" + "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -245,3 +248,23 @@ func TestRepoCacheKey_Consistency(t *testing.T) { assert.Equal(t, key1, key2) assert.Equal(t, key2, key3) } + +func TestRepoCacheKey_BlocksTraversalRef(t *testing.T) { + t.Parallel() + + // A malicious ref containing path traversal components must not be able to make + // the cache directory escape the task-git-repos root (CWE-22, GHSA-g8jx-8vm6-phr8). + node, err := NewGitNode("https://github.com/foo/bar.git//file.yml?ref=../../../../victim-delete-me", "", false) + require.NoError(t, err) + + key := node.repoCacheKey() + + // The key itself must not carry traversal components. + assert.NotContains(t, key, "..", "cache key must not contain traversal components") + + // Joined under the cache root, the resolved path must stay inside it. + root := filepath.Join(os.TempDir(), "task-git-repos") + resolved := filepath.Clean(filepath.Join(root, key)) + assert.True(t, strings.HasPrefix(resolved, root+string(os.PathSeparator)), + "cache dir %q must stay within %q", resolved, root) +}