Writing Security Rules
Author ModSecurity-compatible SecRules as Kubernetes resources
This guide teaches you how to write effective SecRule resources in kubeWAF.
The Structured Format
Instead of writing raw SecLang strings, kubeWAF uses a typed Kubernetes representation. This gives you:
- Syntax validation via Kubernetes schemas (future webhooks)
- Better IDE support and GitOps reviewability
- Automatic conversion to valid ModSecurity SecLang syntax
A SecRule contains one or more entries under spec.secLangRules.
Minimal Example
apiVersion: seclang.kubewaf.io/v1beta1
kind: SecRule
metadata:
name: block-admin-bruteforce
namespace: production
spec:
secLangRules:
- metadata:
id: 200010
phase: "2"
message: "Multiple failed admin logins"
severity: "WARNING"
tags:
- "attack-bruteforce"
- "OWASP_CRS"
conditions:
- variables:
- name: REQUEST_URI
operator:
name: rx
value: ^/admin/login
- variables:
- name: ARGS_POST
collection: password
operator:
name: rx
value: (?:admin|root)
actions:
disruptive:
disruptiveActionType: pass # scoring rule, not blocking
nonDisruptive:
- nonDisruptiveActionType: setvar
value: TX.anomaly_score_pl1=+3Anatomy of a Rule
metadata
| Field | Description | Example |
|---|---|---|
id | Unique numeric rule ID (required for CRS style) | 920100 |
phase | Execution phase (1–5) | "2" |
message | Human-readable description shown in logs | "SQL Injection" |
severity | EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG | ERROR |
tags | Arbitrary classification tags | ["attack-sqli"] |
conditions
Each condition is a combination of variables / collections + operator.
Supported styles:
# Simple variable
variables:
- name: REQUEST_URI
# Collection member
variables:
- name: ARGS_GET
collection: id
# TX collection (transaction variables)
collections:
- name: TX
arguments: [ANOMALY_SCORE]operators
Common operators:
rx— regular expression (most powerful)eq,gt,lt,ge,lestreq,startswith,endsWith,containsipMatch,ipMatchFromFilegeoLookupdetectSQLi,detectXSS(from libinjection in the WAF engine)
Use negate: true to invert the match.
actions
Actions are split into three groups in the CRD:
actions:
disruptive:
disruptiveActionType: deny # or allow, block, drop, pass, redirect
# redirectUrl, status also supported
flow:
- flowActionType: skip
value: "950000"
- flowActionType: skipAfter
value: END-REQUEST-920
nonDisruptive:
- nonDisruptiveActionType: setvar
value: TX.inbound_anomaly_score_pl1=+5
- nonDisruptiveActionType: setvar
value: tx.detection_paranoia_level=2
- nonDisruptiveActionType: logdata
value: "%{REQUEST_URI}"
- nonDisruptiveActionType: msg
value: "Attack detected"Chained Rules
Chained rules allow multi-condition logic (AND).
In the YAML model you simply set chainedRule: true on the first part and the next SecLangSecRule entry becomes the continuation.
The controller and converter handle emitting the chain action correctly.
Example (from CRS):
- metadata: { id: 942100, phase: "2", ... }
conditions: [ ... first condition ... ]
actions:
disruptive: { disruptiveActionType: pass }
chainedRule: true
- metadata: { ... }
conditions: [ ... second condition ... ]
actions:
disruptive: { disruptiveActionType: block }SecMarker and Flow Control
You can create named markers for skipAfter:
- secMarker: END-REQUEST-920-PROTOCOL-ENFORCEMENT
metadata: { id: 0, phase: "1" }
conditions: [{ always-match: true }]
actions:
nonDisruptive: [{ nonDisruptiveActionType: nolog }]Then other rules can skipAfter: END-REQUEST-920-PROTOCOL-ENFORCEMENT.
Best Practices
- Use high rule IDs for your custom rules (> 100000 recommended) to avoid collisions with CRS.
- Tag everything — makes it easy to create RuleSets with selectors later.
- Prefer anomaly scoring over immediate
denyfor better false-positive handling. - Write unit-test equivalents — many teams create a small test HTTPRoute + curl matrix.
- Store rules in Git alongside your application manifests (GitOps).
Writing Rules with AI Assistance
The structured SecRule format is powerful for validation and GitOps, but it is verbose. kubeWAF makes it much easier to create high-quality rules by providing excellent support for AI coding assistants.
Using AI to create SecRules
If you use Grok (the Grok TUI or CLI):
- A dedicated skill called
kubewaf-secruleis automatically available when you are inside this repository. - Just describe what you need in plain language:
- "Create a SecRule that blocks suspicious User-Agents hitting login pages"
- "Write a virtual patch for path traversal on /api/files"
- "Convert this raw SecLang rule to the proper Kubernetes format and validate it"
The skill generates both raw SecLang and the full kind: SecRule YAML, validates it against the Kubernetes API server using kubectl apply --dry-run=server, and can fix issues automatically.
If you use any other AI (Claude, Cursor, GPT-4, Continue.dev, Aider, Windsurf, etc.):
- Copy the content of
docs/ai/kubewaf-secrule-expert.mdand paste it into your conversation or project rules. - Many tools will also automatically load the guidance from the repository's
AGENTS.mdfile when you open the kubewaf repo.
See docs/ai/README.md for usage instructions for different AI tools.
Recommended AI + Validation Workflow
-
Describe the desired protection in natural language.
-
Ask the AI to return both a raw SecLang version and the structured
SecRuleYAML. -
Always validate the generated resource before using it:
kubectl apply -f my-new-rule.yaml --dry-run=serverThis sends the object to the real API server and exercises the CRD schema (plus any validating webhooks).
-
Let the AI also generate a matching
RuleSetthat wires the new rule into your WAF policy.
This combination gives you the speed of natural language with the safety of proper Kubernetes validation.
Converting Existing ModSecurity Rules
If you have existing .conf files in SecLang syntax, you can:
- Use the
crs-convertertool (see Using CRS) - Or manually translate them into the structured YAML format
The converter produces high-fidelity SecRule objects that you can further edit.
Validation & Status
After applying a SecRule, check its status:
kubectl get secrule block-bad-ua -o yamlThe .status.secRuleString field contains the exact SecLang that will be sent to modsecurity-proxy-wasm.
If conversion fails, the Ready condition will be False with a helpful message.
Next
Now that you can write individual rules, learn how to organize them with RuleSets.