39 lines
1.2 KiB
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
|
|
}
|