fix: blocklist fails open when a regex fails to compile (#87)
Fixes #72 ## Why `compilePatterns` silently discards any pattern that fails to compile. A typo in a blocklist entry therefore turns a deny rule into a no-op — a fail-open with security impact. ## Changes - Add `Remote.ValidatePatterns`, which compiles every pattern list (patterns, blocklist, mutable/immutable patterns, ban_tags) and returns an error on the first invalid regex. - Reject invalid patterns with 400 at remote create and update time. - Unit test for valid and invalid patterns. ## Validation - `go test ./pkg/models/` and `make e2e` pass. Reviewed-on: #87 Co-authored-by: Ben Vincent <ben@unkin.net> Co-committed-by: Ben Vincent <ben@unkin.net>
This commit was merged in pull request #87.
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)
|
http.Error(w, "base_url is required for remote repositories", http.StatusBadRequest)
|
||||||
return
|
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 {
|
if err := h.db.CreateRemote(r.Context(), &remote); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -84,6 +88,10 @@ func (h *RemotesHandler) update(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
remote.Name = name
|
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 {
|
if err := h.db.UpdateRemote(r.Context(), &remote); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,6 +67,30 @@ type Remote struct {
|
|||||||
UpdatedAt time.Time `json:"updated_at"`
|
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 {
|
type RemoteWithStats struct {
|
||||||
Remote
|
Remote
|
||||||
Stats RemoteStats `json:"stats"`
|
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