Harden Windows package paths (T-605)

This commit is contained in:
ila
2026-07-16 23:56:19 +08:00
parent f7a803d944
commit 6fd19d0f43
19 changed files with 664 additions and 108 deletions
+5 -17
View File
@@ -7,13 +7,12 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"sync"
"softbox.local/core/domain"
"softbox.local/core/internal/safepath"
)
const (
@@ -281,12 +280,13 @@ func (record InstalledApp) validate() error {
seenPaths := make(map[string]struct{}, len(record.Files))
for index, installedFile := range record.Files {
if !validInstalledPath(installedFile.Path) {
if err := safepath.ValidateRelative(installedFile.Path); err != nil {
return fmt.Errorf(
"%w: files[%d].path=%q",
"%w: files[%d].path=%q: %v",
ErrInstalledAppInvalid,
index,
installedFile.Path,
err,
)
}
if installedFile.Size < 0 {
@@ -304,7 +304,7 @@ func (record InstalledApp) validate() error {
index,
)
}
foldedPath := strings.ToLower(installedFile.Path)
foldedPath := safepath.CollisionKey(installedFile.Path)
if _, exists := seenPaths[foldedPath]; exists {
return fmt.Errorf(
"%w: duplicate file path %q",
@@ -317,18 +317,6 @@ func (record InstalledApp) validate() error {
return nil
}
func validInstalledPath(value string) bool {
if value == "" || strings.Contains(value, `\`) || strings.Contains(value, ":") {
return false
}
cleaned := path.Clean(value)
return cleaned == value &&
cleaned != "." &&
!strings.HasPrefix(cleaned, "/") &&
cleaned != ".." &&
!strings.HasPrefix(cleaned, "../")
}
func requireRealDirectory(directory string) error {
info, err := os.Lstat(directory)
if err != nil {
+52
View File
@@ -74,6 +74,42 @@ func TestInstalledAppStoreRejectsInvalidRecords(t *testing.T) {
record.Files[0].Path = "../escape.exe"
},
},
{
name: "Windows normalized escape",
mutate: func(record *InstalledApp) {
record.Files[0].Path = ".. /escape.exe"
},
},
{
name: "trailing period",
mutate: func(record *InstalledApp) {
record.Files[0].Path = "JsonParser.exe."
},
},
{
name: "trailing space",
mutate: func(record *InstalledApp) {
record.Files[0].Path = "JsonParser.exe "
},
},
{
name: "leading space",
mutate: func(record *InstalledApp) {
record.Files[0].Path = " JsonParser.exe"
},
},
{
name: "reserved device",
mutate: func(record *InstalledApp) {
record.Files[0].Path = "NUL.txt"
},
},
{
name: "forbidden Windows character",
mutate: func(record *InstalledApp) {
record.Files[0].Path = "Json?Parser.exe"
},
},
{
name: "invalid hash",
mutate: func(record *InstalledApp) {
@@ -103,6 +139,22 @@ func TestInstalledAppStoreRejectsInvalidRecords(t *testing.T) {
}
}
func TestInstalledAppStoreAcceptsUnicodeNestedPath(t *testing.T) {
record := validInstalledApp()
record.Files[0].Path = "工具/解析器.exe"
store := NewInstalledAppStore(filepath.Join(t.TempDir(), "apps"))
if err := store.Write(record); err != nil {
t.Fatalf("Write() error = %v", err)
}
loaded, found, err := store.Read(record.ID)
if err != nil {
t.Fatalf("Read() error = %v", err)
}
if !found || loaded.Files[0].Path != record.Files[0].Path {
t.Fatalf("Read() = %#v, found=%t", loaded, found)
}
}
func TestInstalledAppStoreRejectsUnknownFieldsAndIDMismatch(t *testing.T) {
appsRoot := filepath.Join(t.TempDir(), "apps")
appRoot := filepath.Join(appsRoot, "json-parser")