fix: validate remote regex patterns at write time
compilePatterns silently drops any pattern that fails to compile, so a mistyped blocklist entry became a no-op (fail-open). Reject invalid patterns when a remote is created or updated via Remote.ValidatePatterns, returning 400 instead of storing a rule that never matches. Refs #72
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user