mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
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:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user