{"record":{"id":"ac4a7fd84bd4a87f","repo":"JuliusBrussee/caveman","slug":"w-rule-q-s","errorCode":null,"errorMessage":"%w: rule %q: %s","messagePattern":"%w: rule %q: (.+?)","errorType":"validation","errorClass":"ErrInvalidRule","httpStatus":null,"severity":"error","filePath":"shared/platform/redact/payload.go","lineNumber":586,"sourceCode":"\t\t\t// A reference to the unconditional floor. Nothing to run, and\n\t\t\t// nothing it could switch off.\n\t\t\tcontinue\n\t\tcase RuleTypeRegex:\n\t\tcase RuleTypeJSONPath, RuleTypeHeader:\n\t\t\treturn nil, \"\", fmt.Errorf(\"%w: %q (rule %q)\", ErrRuleUnsupported, r.Type, r.Name)\n\t\tdefault:\n\t\t\treturn nil, \"\", fmt.Errorf(\"%w: %q (rule %q)\", ErrRuleUnsupported, r.Type, r.Name)\n\t\t}\n\n\t\tif strings.TrimSpace(r.Name) == \"\" {\n\t\t\treturn nil, \"\", fmt.Errorf(\"%w: empty name\", ErrInvalidRule)\n\t\t}\n\t\tif r.Pattern == \"\" {\n\t\t\treturn nil, \"\", fmt.Errorf(\"%w: rule %q has an empty pattern\", ErrInvalidRule, r.Name)\n\t\t}\n\t\tre, err := regexp.Compile(r.Pattern)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", fmt.Errorf(\"%w: rule %q: %s\", ErrInvalidRule, r.Name, err)\n\t\t}\n\t\tif re.MatchString(\"\") {\n\t\t\t// Such a pattern matches at every position and would replace the\n\t\t\t// whole body with placeholders. Refuse it rather than destroy the\n\t\t\t// capture.\n\t\t\treturn nil, \"\", fmt.Errorf(\"%w: rule %q matches the empty string\", ErrInvalidRule, r.Name)\n\t\t}\n\t\trepl := r.Replacement\n\t\tif repl == \"\" {\n\t\t\trepl = \"[REDACTED:\" + r.Name + \"]\"\n\t\t}\n\t\tout = append(out, compiledRule{\n\t\t\tname:                 r.Name,\n\t\t\torigin:               OriginOrg,\n\t\t\tre:                   re,\n\t\t\treplIntroducesNeedle: introducesNeedle([]byte(repl)),\n\t\t\t// The replacement is operator-supplied data, not a regexp\n\t\t\t// template: a literal replace keeps \"$1\" from expanding a captured","sourceCodeStart":568,"sourceCodeEnd":604,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/redact/payload.go#L568-L604","documentation":"regexp.Compile failed for the rule's Pattern; the error wraps ErrInvalidRule and includes the rule's Name plus the underlying regexp/syntax error text (position and reason). The Go RE2 engine rejects several constructs common elsewhere: backreferences ((\\1)), lookaround ((?=...)), and some invalid repetition/escape spellings.","triggerScenarios":"A rule authored with PCRE syntax — e.g. Pattern: `\"(?<=token:)\\s+\\w+\"` or `(a+)\\1` — passed to redact.Payload; the compile step runs before any body is processed, so the error fires on the first call regardless of body content.","commonSituations":"Porting redaction rules written for PCRE/Perl/ripgrep-style engines; regex validated in a JS UI (different dialect) and deployed to the Go backend; unescaped user input interpolated into a pattern (a stray '(' breaks it).","solutions":["Rewrite the pattern for RE2: replace lookbehind with a captured group (token:\\s*(\\w+)) and backreferences with explicit alternation or a different strategy.","Test patterns with regexp.Compile in a unit test over the rule fixture so syntax errors surface in CI, not in the capture path.","If the pattern came from string interpolation, use regexp.QuoteMeta for literal parts."],"exampleFix":"// before\n{Name: \"after-token\", Type: redact.RuleTypeRegex, Pattern: `(?<=token:)[A-Za-z0-9]+`} // lookbehind unsupported\n\n// after\n{Name: \"after-token\", Type: redact.RuleTypeRegex, Pattern: `token:\\s*([A-Za-z0-9]+)`} // capture group, replacement keeps $-free literal","handlingStrategy":"validation","validationCode":"func compileAllRules(rules []redact.Rule) error {\n    for _, r := range rules {\n        if r.Type != redact.RuleTypeRegex { continue }\n        if _, err := regexp.Compile(r.Pattern); err != nil {\n            return fmt.Errorf(\"rule %q: %w\", r.Name, err)\n        }\n    }\n    return nil\n}","typeGuard":"func compiles(p string) bool { _, err := regexp.Compile(p); return err == nil }","tryCatchPattern":"if _, _, err := redact.Payload(body, rules); err != nil {\n    if errors.Is(err, redact.ErrInvalidRule) && strings.Contains(err.Error(), \": error parsing\") {\n        // rewrite the pattern for RE2 (no lookaround/backreferences) and redeploy rules\n    }\n}","preventionTips":["Author patterns for Go's RE2 dialect; test every rule with regexp.Compile in CI.","Never interpolate unescaped user text into a pattern — use regexp.QuoteMeta for literal parts.","If coming from PCRE, mechanically replace lookbehind with capture groups and drop backreferences."],"tags":["redaction","regex","re2","config"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}