Merge commit from fork

This commit is contained in:
Valentin Maerten
2026-07-14 22:06:38 +02:00
committed by GitHub
parent 6833ee5c0c
commit 26baea3dcd
2 changed files with 30 additions and 2 deletions

View File

@@ -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/<sha256-hex>
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) {

View File

@@ -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)
}