diff --git a/internal/security/detector.go b/internal/security/detector.go index 4d42329cd..d8ded82d4 100644 --- a/internal/security/detector.go +++ b/internal/security/detector.go @@ -1,6 +1,7 @@ package security import ( + "regexp" "strings" "sync" "time" @@ -344,6 +345,11 @@ func buildCustomPattern(cp config.CustomPattern) *Pattern { // Set pattern (regex or keywords) if cp.Regex != "" { + // Config is user-edited and not validated on load; an invalid regex + // must drop this one pattern, not panic in WithRegex's MustCompile. + if _, err := regexp.Compile(cp.Regex); err != nil { + return nil + } builder.WithRegex(cp.Regex) } else if len(cp.Keywords) > 0 { builder.WithKeywords(cp.Keywords...) diff --git a/internal/security/detector_custom_regex_test.go b/internal/security/detector_custom_regex_test.go new file mode 100644 index 000000000..274aabf99 --- /dev/null +++ b/internal/security/detector_custom_regex_test.go @@ -0,0 +1,39 @@ +package security + +import ( + "testing" + + "github.com/smart-mcp-proxy/mcpproxy-go/internal/config" +) + +// sensitive_data_detection.custom_patterns comes straight from the user's +// config file; one bad regex must not take down NewDetector (startup, config +// reload, credential screen) or the other custom patterns. +func TestNewDetector_InvalidCustomRegexIsSkipped(t *testing.T) { + cfg := config.DefaultSensitiveDataDetectionConfig() + cfg.CustomPatterns = []config.CustomPattern{ + {Name: "broken", Regex: "("}, + {Name: "ticket", Regex: `TICKET-\d{6}`}, + } + + var d *Detector + func() { + defer func() { + if r := recover(); r != nil { + t.Fatalf("NewDetector panicked on an invalid custom regex: %v", r) + } + }() + d = NewDetector(cfg) + }() + + res := d.Scan(`{"note":"see TICKET-123456"}`, "") + found := false + for _, det := range res.Detections { + if det.Type == "ticket" { + found = true + } + } + if !found { + t.Errorf("valid custom pattern next to the broken one was not applied: %+v", res.Detections) + } +}