From a3bee13ca9d9cacbb1346f872dad4e80a0fef544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AE=B6=E5=90=8D?= Date: Mon, 15 Jun 2026 19:14:31 +0800 Subject: [PATCH] fix(vfs): reject blank local paths (#1460) --- internal/validate/path_test.go | 4 ++++ internal/vfs/localfileio/path.go | 4 ++++ internal/vfs/localfileio/path_test.go | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/internal/validate/path_test.go b/internal/validate/path_test.go index b99a16a8..61e63341 100644 --- a/internal/validate/path_test.go +++ b/internal/validate/path_test.go @@ -26,6 +26,10 @@ func TestSafeOutputPath_RejectsPathTraversalAndDangerousInput(t *testing.T) { {"unicode normal", "报告.xlsx", false}, {"dot-dot resolves to cwd", "subdir/..", false}, + // ── GIVEN: empty or blank paths → THEN: rejected ── + {"empty path", "", true}, + {"blank path", " ", true}, + // ── GIVEN: path traversal via .. → THEN: rejected ── {"dot-dot escape", "../../.ssh/authorized_keys", true}, {"dot-dot mid path", "subdir/../../etc/passwd", true}, diff --git a/internal/vfs/localfileio/path.go b/internal/vfs/localfileio/path.go index d0209737..1f343eeb 100644 --- a/internal/vfs/localfileio/path.go +++ b/internal/vfs/localfileio/path.go @@ -60,6 +60,10 @@ func safePath(raw, flagName string) (string, error) { return "", err } + if strings.TrimSpace(raw) == "" { + return "", fmt.Errorf("%s must not be empty", flagName) + } + 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) } diff --git a/internal/vfs/localfileio/path_test.go b/internal/vfs/localfileio/path_test.go index 3cfb8e74..9043fd78 100644 --- a/internal/vfs/localfileio/path_test.go +++ b/internal/vfs/localfileio/path_test.go @@ -26,6 +26,10 @@ func TestSafeOutputPath_RejectsPathTraversalAndDangerousInput(t *testing.T) { {"unicode normal", "报告.xlsx", false}, {"dot-dot resolves to cwd", "subdir/..", false}, + // ── GIVEN: empty or blank paths → THEN: rejected ── + {"empty path", "", true}, + {"blank path", " ", true}, + // ── GIVEN: path traversal via .. → THEN: rejected ── {"dot-dot escape", "../../.ssh/authorized_keys", true}, {"dot-dot mid path", "subdir/../../etc/passwd", true},