diff --git a/internal/validate/path_test.go b/internal/validate/path_test.go index b99a16a85..61e63341f 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 d02097373..1f343eeb6 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 3cfb8e740..9043fd78d 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},