diff --git a/internal/api/v2/remotes.go b/internal/api/v2/remotes.go index 7b927ad..6ebd8ba 100644 --- a/internal/api/v2/remotes.go +++ b/internal/api/v2/remotes.go @@ -69,6 +69,10 @@ func (h *RemotesHandler) create(w http.ResponseWriter, r *http.Request) { http.Error(w, "base_url is required for remote repositories", http.StatusBadRequest) return } + if err := remote.ValidatePatterns(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } if err := h.db.CreateRemote(r.Context(), &remote); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -84,6 +88,10 @@ func (h *RemotesHandler) update(w http.ResponseWriter, r *http.Request) { return } remote.Name = name + if err := remote.ValidatePatterns(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } if err := h.db.UpdateRemote(r.Context(), &remote); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/pkg/models/remote.go b/pkg/models/remote.go index 6e380e8..953336d 100644 --- a/pkg/models/remote.go +++ b/pkg/models/remote.go @@ -2,6 +2,7 @@ package models import ( "fmt" + "regexp" "time" ) @@ -66,6 +67,30 @@ type Remote struct { UpdatedAt time.Time `json:"updated_at"` } +// ValidatePatterns ensures every configured regex compiles. Storing an +// invalid pattern would otherwise be silently dropped at match time, which +// for the blocklist is a fail-open: a mistyped deny rule becomes a no-op. +func (r *Remote) ValidatePatterns() error { + groups := []struct { + field string + patterns []string + }{ + {"patterns", r.Patterns}, + {"blocklist", r.Blocklist}, + {"mutable_patterns", r.MutablePatterns}, + {"immutable_patterns", r.ImmutablePatterns}, + {"ban_tags", r.BanTags}, + } + for _, g := range groups { + for _, p := range g.patterns { + if _, err := regexp.Compile(p); err != nil { + return fmt.Errorf("invalid regex in %s: %q: %w", g.field, p, err) + } + } + } + return nil +} + type RemoteWithStats struct { Remote Stats RemoteStats `json:"stats"` diff --git a/pkg/models/remote_test.go b/pkg/models/remote_test.go new file mode 100644 index 0000000..e46de7b --- /dev/null +++ b/pkg/models/remote_test.go @@ -0,0 +1,19 @@ +package models + +import "testing" + +func TestRemote_ValidatePatterns(t *testing.T) { + valid := &Remote{ + Patterns: []string{`.*\.tar\.gz$`}, + Blocklist: []string{`^secret/`}, + ImmutablePatterns: []string{`\.rpm$`}, + } + if err := valid.ValidatePatterns(); err != nil { + t.Fatalf("expected valid patterns, got %v", err) + } + + bad := &Remote{Blocklist: []string{`[unterminated`}} + if err := bad.ValidatePatterns(); err == nil { + t.Fatal("expected error for invalid blocklist regex, got nil") + } +}