fix(vfs): reject Windows absolute paths cross-platform (#1401)

* fix(vfs): reject Windows absolute paths cross-platform

* test(vfs): cover input Windows absolute paths
This commit is contained in:
hhang
2026-06-13 18:56:13 +08:00
committed by GitHub
parent deb0bd9dd6
commit 751092c8ef
2 changed files with 39 additions and 8 deletions

View File

@@ -60,12 +60,12 @@ func safePath(raw, flagName string) (string, error) {
return "", err
}
path := filepath.Clean(raw)
if filepath.IsAbs(path) {
if isAbsolutePath(raw) {
return "", fmt.Errorf("%s must be a relative path within the current directory, got %q (hint: cd to the target directory first, or use a relative path like ./filename)", flagName, raw)
}
path := filepath.Clean(raw)
cwd, err := vfs.Getwd()
if err != nil {
return "", fmt.Errorf("cannot determine working directory: %w", err)
@@ -114,6 +114,21 @@ func resolveNearestAncestor(path string) (string, error) {
}
}
func isAbsolutePath(path string) bool {
path = strings.TrimSpace(path)
if path == "" {
return false
}
if filepath.IsAbs(path) || strings.HasPrefix(path, "/") || strings.HasPrefix(path, `\`) {
return true
}
if len(path) >= 3 && path[1] == ':' && (path[2] == '/' || path[2] == '\\') {
drive := path[0]
return ('A' <= drive && drive <= 'Z') || ('a' <= drive && drive <= 'z')
}
return false
}
func isUnderDir(child, parent string) bool {
rel, err := filepath.Rel(parent, child)
if err != nil {

View File

@@ -34,6 +34,10 @@ func TestSafeOutputPath_RejectsPathTraversalAndDangerousInput(t *testing.T) {
// ── GIVEN: absolute paths → THEN: rejected ──
{"absolute path unix", "/etc/passwd", true},
{"absolute path root", "/tmp/evil", true},
{"absolute path windows drive", `C:\Users\agent\secret.txt`, true},
{"absolute path windows drive slash", "C:/Users/agent/secret.txt", true},
{"absolute path windows rooted", `\Users\agent\secret.txt`, true},
{"absolute path windows unc", `\\server\share\secret.txt`, true},
// ── GIVEN: control characters in path → THEN: rejected ──
{"null byte", "file\x00.txt", true},
@@ -187,11 +191,23 @@ func TestSafeUploadPath_AllowsTempFileAbsolutePath(t *testing.T) {
}
func TestSafeUploadPath_RejectsNonTempAbsolutePath(t *testing.T) {
// GIVEN: an absolute path outside the temp directory
// WHEN / THEN: SafeUploadPath rejects it
_, err := SafeInputPath("/etc/passwd")
if err == nil {
t.Error("expected error for absolute non-temp path, got nil")
for _, tt := range []struct {
name string
input string
}{
{"absolute path unix", "/etc/passwd"},
{"absolute path windows drive", `C:\Users\agent\secret.txt`},
{"absolute path windows drive slash", "C:/Users/agent/secret.txt"},
{"absolute path windows rooted", `\Users\agent\secret.txt`},
{"absolute path windows unc", `\\server\share\secret.txt`},
} {
t.Run(tt.name, func(t *testing.T) {
// WHEN / THEN: SafeInputPath rejects absolute paths on every platform.
_, err := SafeInputPath(tt.input)
if err == nil {
t.Errorf("expected error for absolute path %q, got nil", tt.input)
}
})
}
}