Consolidate path safety, temp downloads, and cleanup validation helpers

This commit is contained in:
2026-05-23 13:20:28 +00:00
parent ea87c335d6
commit 572a112c31
15 changed files with 405 additions and 197 deletions

View File

@@ -13,6 +13,7 @@ import (
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/config"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
"gitea.maximumdirect.net/eric/narratio/internal/pathsafe"
)
const (
@@ -106,7 +107,7 @@ func BuildPlan(
return result, nil
}
runIDTemp, err := downloadObjectToTemp(ctx, store, currentRunIDKey, "narratio-previous-run-id-*.txt")
runIDTemp, err := storage.DownloadObjectToTemp(ctx, store, currentRunIDKey, "narratio-previous-run-id-*.txt")
if err != nil {
return nil, fmt.Errorf("download previous-session current run pointer %q: %w", currentRunIDKey, err)
}
@@ -134,7 +135,7 @@ func BuildPlan(
return result, nil
}
manifestTemp, err := downloadObjectToTemp(ctx, store, currentManifestKey, "narratio-previous-manifest-*.json")
manifestTemp, err := storage.DownloadObjectToTemp(ctx, store, currentManifestKey, "narratio-previous-manifest-*.json")
if err != nil {
return nil, fmt.Errorf("download previous-session current manifest %q: %w", currentManifestKey, err)
}
@@ -278,7 +279,7 @@ func artifactRelativePathCandidates(
) []string {
candidates := []string{}
appendCandidate := func(v string) {
normalized, err := normalizeArchiveRelativePath(v)
normalized, err := pathsafe.NormalizeRelativeDestination(v)
if err != nil {
return
}
@@ -354,7 +355,7 @@ func deriveManifestRelativePath(previousManifest *manifest.Manifest, localPath s
return "", false
}
if !filepath.IsAbs(trimmed) {
normalized, err := normalizeArchiveRelativePath(filepath.ToSlash(trimmed))
normalized, err := pathsafe.NormalizeRelativeDestination(filepath.ToSlash(trimmed))
if err != nil {
return "", false
}
@@ -369,7 +370,7 @@ func deriveManifestRelativePath(previousManifest *manifest.Manifest, localPath s
if err != nil {
return "", false
}
normalized, err := normalizeArchiveRelativePath(filepath.ToSlash(rel))
normalized, err := pathsafe.NormalizeRelativeDestination(filepath.ToSlash(rel))
if err != nil {
return "", false
}
@@ -417,7 +418,7 @@ func manifestPublishedPaths(previousManifest *manifest.Manifest) []string {
if !ok {
continue
}
normalized, err := normalizeArchiveRelativePath(asString)
normalized, err := pathsafe.NormalizeRelativeDestination(asString)
if err != nil {
continue
}
@@ -426,21 +427,6 @@ func manifestPublishedPaths(previousManifest *manifest.Manifest) []string {
return dedupeOrderedStrings(out)
}
func normalizeArchiveRelativePath(rel string) (string, error) {
trimmed := strings.TrimSpace(rel)
if trimmed == "" {
return "", fmt.Errorf("relative path is required")
}
cleaned := filepath.ToSlash(filepath.Clean(filepath.FromSlash(trimmed)))
if cleaned == "." || cleaned == "" {
return "", fmt.Errorf("relative path is required")
}
if filepath.IsAbs(trimmed) || strings.HasPrefix(cleaned, "/") || cleaned == ".." || strings.HasPrefix(cleaned, "../") {
return "", fmt.Errorf("path must be a clean relative path")
}
return cleaned, nil
}
func relativeToSession(paths artifacts.SessionPaths, localPath string) (string, error) {
root := filepath.Clean(paths.Root)
if strings.TrimSpace(root) == "" {
@@ -450,7 +436,7 @@ func relativeToSession(paths artifacts.SessionPaths, localPath string) (string,
if err != nil {
return "", fmt.Errorf("resolve previous-cache relative path: %w", err)
}
normalized, err := normalizeArchiveRelativePath(filepath.ToSlash(rel))
normalized, err := pathsafe.NormalizeRelativeDestination(filepath.ToSlash(rel))
if err != nil {
return "", fmt.Errorf("resolve previous-cache relative path: %w", err)
}
@@ -476,20 +462,3 @@ func dedupeOrderedStrings(values []string) []string {
}
return out
}
func downloadObjectToTemp(ctx context.Context, store storage.ObjectStore, key, pattern string) (string, error) {
tmp, err := os.CreateTemp("", pattern)
if err != nil {
return "", fmt.Errorf("create temp file: %w", err)
}
path := tmp.Name()
if err := tmp.Close(); err != nil {
_ = os.Remove(path)
return "", fmt.Errorf("close temp file: %w", err)
}
if err := store.Download(ctx, key, path); err != nil {
_ = os.Remove(path)
return "", err
}
return path, nil
}