{"record":{"id":"b9d8b970350d4dcd","repo":"crowdsecurity/crowdsec","slug":"no-zones-defined","errorCode":null,"errorMessage":"no zones defined","messagePattern":"no zones defined","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/appsec/appsec_rule/appsec_rule.go","lineNumber":53,"sourceCode":"type CustomRule struct {\n\tName      string   `yaml:\"name\"`\n\tSeverity  string   `yaml:\"severity\"`\n\tZones     []string `yaml:\"zones\"`\n\tVariables []string `yaml:\"variables\"`\n\n\tMatch     Match        `yaml:\"match\"`\n\tTransform []string     `yaml:\"transform\"` //t:lowercase, t:uppercase, etc\n\tAnd       []CustomRule `yaml:\"and,omitempty\"`\n\tOr        []CustomRule `yaml:\"or,omitempty\"`\n\n\tBodyType string `yaml:\"body_type,omitempty\"`\n}\n\n// Convert renders the rule; ruleIndex is its position in the collection, used\n// to keep ids unique across rules that share identical leaves.\nfunc (v *CustomRule) Convert(ruleType string, appsecRuleName string, appsecRuleDescription string, ruleIndex int) (string, []uint32, error) {\n\tif v.Zones == nil && v.And == nil && v.Or == nil {\n\t\treturn \"\", nil, errors.New(\"no zones defined\")\n\t}\n\n\tif v.Match.Type == \"\" && v.And == nil && v.Or == nil {\n\t\treturn \"\", nil, errors.New(\"no match type defined\")\n\t}\n\n\tif v.Match.Value == \"\" && v.And == nil && v.Or == nil {\n\t\treturn \"\", nil, errors.New(\"no match value defined\")\n\t}\n\n\tswitch ruleType {\n\tcase ModsecurityRuleType:\n\t\tr := ModsecurityRule{}\n\t\treturn r.Build(v, appsecRuleName, appsecRuleDescription, ruleIndex)\n\tdefault:\n\t\treturn \"\", nil, fmt.Errorf(\"unknown rule format '%s'\", ruleType)\n\t}\n}","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/appsec_rule/appsec_rule.go#L35-L71","documentation":"CustomRule.Convert validates that an appsec rule has at least one way to match: a zones list, an `and` group, or an `or` group. A rule with none of these cannot be compiled into a modsecurity/Coraza rule, so Convert returns this error and the whole rule collection fails to load.","triggerScenarios":"Defining an appsec rule entry with empty `zones:`, no `and:` and no `or:` keys (e.g. only `match:` given), then calling Convert during collection loading.","commonSituations":"A hand-written YAML rule where the zones block was accidentally deleted or indented so it is not parsed into the CustomRule struct; a rule with only `match: {type: regex, value: ...}` but no zone.","solutions":["Add a zones list to the rule, e.g. `zones: [URI]`","Or wrap the matching conditions in an `and:` / `or:` group if combining conditions","Check YAML indentation so the zones field is actually attached to the rule object"],"exampleFix":"// before\n- match:\n    type: contains\n    value: bad-string\n\n// after\n- zones:\n    - URI\n  match:\n    type: contains\n    value: bad-string","handlingStrategy":"validation","validationCode":"func (r CustomRule) hasMatcher() bool {\n    return r.Zones != nil || len(r.And) > 0 || len(r.Or) > 0\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Every appsec rule leaf needs zones; keep a schema/CI validation of rule YAML before hub push","Check indentation so zones parse into the rule struct, not a sibling key"],"tags":["appsec","rules","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}