verstak/internal/core/sync/safe_path.go

39 lines
1.2 KiB
Go

package sync
import (
"fmt"
"path/filepath"
"strings"
)
// SafeVaultPath validates that relPath is a safe relative path within vaultRoot.
// It rejects absolute paths, paths with ".." that escape vaultRoot, and empty paths.
func SafeVaultPath(vaultRoot, relPath string) (string, error) {
if relPath == "" {
return "", fmt.Errorf("empty path")
}
if filepath.IsAbs(relPath) {
return "", fmt.Errorf("absolute path not allowed: %s", relPath)
}
clean := filepath.Clean(relPath)
if strings.HasPrefix(clean, "..") || strings.Contains(clean, "../") || strings.HasPrefix(clean, "\\..") {
return "", fmt.Errorf("path escapes vault: %s", relPath)
}
joined := filepath.Join(vaultRoot, clean)
// Verify we're still inside vaultRoot after Clean.
if !strings.HasPrefix(joined, filepath.Clean(vaultRoot)+string(filepath.Separator)) && joined != filepath.Clean(vaultRoot) {
return "", fmt.Errorf("path escapes vault after join: %s", relPath)
}
return clean, nil
}
// SafeVaultPaths validates multiple paths and returns the first error.
func SafeVaultPaths(vaultRoot string, paths ...string) error {
for _, p := range paths {
if _, err := SafeVaultPath(vaultRoot, p); err != nil {
return err
}
}
return nil
}