diff --git a/internal/storage/local.go b/internal/storage/local.go index a159f02..61f0cf5 100644 --- a/internal/storage/local.go +++ b/internal/storage/local.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "strings" ) // LocalStorage implements StorageBackend using the local filesystem. @@ -87,5 +88,10 @@ func isSubPath(base, target string) bool { if err != nil { return false } - return rel != ".." && !filepath.IsAbs(rel) && rel[:2] != ".." + if filepath.IsAbs(rel) { + return false + } + // Reject paths that escape the base directory. + // filepath.Rel returns ".." or starts with "../" for paths outside base. + return rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) } diff --git a/internal/storage/local_test.go b/internal/storage/local_test.go index 6818ac4..a815847 100644 --- a/internal/storage/local_test.go +++ b/internal/storage/local_test.go @@ -128,3 +128,21 @@ func TestOpenMissingFile(t *testing.T) { t.Errorf("expected 'not found on disk' error, got: %v", err) } } + +func TestIsSubPath_SameDirectory(t *testing.T) { + if !isSubPath("/base", "/base") { + t.Error("isSubPath should return true when base == target") + } +} + +func TestIsSubPath_ShortChildName(t *testing.T) { + if !isSubPath("/base", "/base/a") { + t.Error("isSubPath should return true for single-char child path") + } +} + +func TestIsSubPath_DotDotPrefixFilename(t *testing.T) { + if !isSubPath("/base", "/base/..foo") { + t.Error("isSubPath should return true for filename starting with ..") + } +}