fix: address volume scopes review findings

- CRITICAL: validate volume Name against path traversal (safe regex)
- HIGH: log data migration errors instead of silently ignoring
- HIGH: reject empty source when switching from ephemeral scope
This commit is contained in:
2026-03-31 23:31:27 +03:00
parent 8fb959f81f
commit bb2729ad12
2 changed files with 24 additions and 4 deletions
+13
View File
@@ -5,6 +5,7 @@ import (
"log/slog"
"net/http"
"path/filepath"
"regexp"
"strings"
"github.com/go-chi/chi/v5"
@@ -12,6 +13,9 @@ import (
"github.com/alexei/docker-watcher/internal/store"
)
// safeNamePattern restricts volume names to alphanumeric, dash, underscore, and dot.
var safeNamePattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]*$`)
// validateVolumePath checks that the source path does not contain path traversal.
func validateVolumePath(source string) bool {
cleaned := filepath.Clean(source)
@@ -41,6 +45,9 @@ func validateVolumeScope(scope, name string) string {
if (scope == "project_named" || scope == "named") && strings.TrimSpace(name) == "" {
return "name is required for " + scope + " scope"
}
if name != "" && !safeNamePattern.MatchString(name) {
return "name must start with a letter or digit and contain only letters, digits, dashes, underscores, or dots"
}
return ""
}
@@ -236,6 +243,12 @@ func (s *Server) updateVolume(w http.ResponseWriter, r *http.Request) {
updated.Name = strings.TrimSpace(req.Name)
}
// Non-ephemeral scopes require a source path.
if updated.Scope != "ephemeral" && updated.Source == "" {
respondError(w, http.StatusBadRequest, "source is required for non-ephemeral scopes")
return
}
if err := s.store.UpdateVolume(updated); err != nil {
slog.Error("failed to update volume", "volume_id", volID, "error", err)
respondError(w, http.StatusInternalServerError, "failed to update volume")