{"record":{"id":"ce82ca3aa25e4bb0","repo":"JuliusBrussee/caveman","slug":"w-empty-name","errorCode":null,"errorMessage":"%w: empty name","messagePattern":"%w: empty name","errorType":"validation","errorClass":"ErrInvalidRule","httpStatus":null,"severity":"error","filePath":"shared/platform/redact/payload.go","lineNumber":579,"sourceCode":"\t\tsb.WriteString(r.Pattern)\n\t\tsb.WriteByte('\\x1f')\n\t\tsb.WriteString(r.Replacement)\n\t\tsb.WriteByte('\\x1e')\n\n\t\tswitch r.Type {\n\t\tcase RuleTypeBuiltin:\n\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}","sourceCodeStart":561,"sourceCodeEnd":597,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/redact/payload.go#L561-L597","documentation":"A regex rule reached the name/pattern validation with a Name that is empty or only whitespace (TrimSpace(name) == \"\"). Names are load-bearing here: the default replacement placeholder is '[REDACTED:<name>]' and the rule fingerprint/report identify rules by name, so an unnamed rule cannot be audited or distinguished in the RedactionReport.","triggerScenarios":"compileOrgRules iterates a rule list where a RuleTypeRegex entry has Name: \"\" or \"   \" — commonly from config where the name key was misspelled (label vs name), omitted, or a default struct was copied.","commonSituations":"YAML key mismatch (pattern present, name forgotten); programmatic rule construction that fills Pattern but never Name; refactors that renamed the Name field and left zero values.","solutions":["Give every regex rule a unique, non-blank Name before calling Payload.","If rules come from config, validate names at load time (non-empty, unique) with a line-numbered error instead of failing at redaction time.","Add a struct-level check or linter/test over the rule set fixture."],"exampleFix":"// before\nrules := []redact.Rule{{Type: redact.RuleTypeRegex, Pattern: `Bearer [A-Za-z0-9._-]+`}} // no Name\n\n// after\nrules := []redact.Rule{{Name: \"bearer-token\", Type: redact.RuleTypeRegex, Pattern: `Bearer [A-Za-z0-9._-]+`}}","handlingStrategy":"validation","validationCode":"func validateRuleNames(rules []redact.Rule) error {\n    for _, r := range rules {\n        if r.Type == redact.RuleTypeRegex && strings.TrimSpace(r.Name) == \"\" {\n            return fmt.Errorf(\"regex rule with empty name (pattern %q)\", r.Pattern)\n        }\n    }\n    return nil\n}","typeGuard":"func hasName(r redact.Rule) bool { return strings.TrimSpace(r.Name) != \"\" }","tryCatchPattern":"if _, _, err := redact.Payload(body, rules); err != nil {\n    if errors.Is(err, redact.ErrInvalidRule) && strings.Contains(err.Error(), \"empty name\") {\n        // point the rule author at the offending entry\n    }\n}","preventionTips":["Require non-empty unique names in the rules JSON Schema.","Remember the name lands in '[REDACTED:<name>]' — make names self-documenting.","Validate the rule set once at startup, not per capture."],"tags":["redaction","validation","config"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}