kubeWAF is under active development — feedback and stars on GitHub are very welcome!
kubeWAFkubeWAF
kubeWAFmodsecurity-proxy-wasmpow-proxy-wasm

SecLang YAML Structure

Structured SecLang representation used by kubeWAF CRDs

This page documents the structured representation used in SecRule.spec.secLangRules[].

Top-Level Rule

- metadata: SecRuleMetadata
  conditions: []Condition
  actions: SecRuleActions
  chainedRule: bool
  secMarker: string

Metadata

metadata:
  id: 942100
  phase: "2"
  message: "SQL Injection Attack Detected"
  severity: CRITICAL
  tags:
    - attack-sqli
    - OWASP_CRS
    - paranoia-level/2

Supported severities: EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.

Conditions

A condition describes when the rule should trigger.

Variable Form

conditions:
- variables:
  - name: REQUEST_URI
  - name: ARGS_GET
    collection: id           # optional member of collection
  operator:
    name: rx
    value: (?:union|select|--)
    negate: false

Collection / TX Form (common in CRS)

conditions:
- collections:
  - name: TX
    arguments:
    - DETECTION_PARANOIA_LEVEL
  operator:
    name: lt
    value: "1"

Always-Match (initialization rules)

conditions:
- always-match: true

Operators

NameExample ValueNotes
rx(?i)select.*fromMost common
streq, contains, startswith, endsWithadminString ops
eq, gt, ge, lt, le5Numeric
ipMatch192.168.0.0/16,10.0.0.0/8
detectSQLi(no value)Uses libinjection
detectXSS(no value)
geoLookup(no value)Requires GeoIP data in Wasm image

Actions

Disruptive Actions

These decide whether the request continues:

disruptive:
  disruptiveActionType: deny | block | allow | pass | drop | redirect
  status: "403"                    # for deny/block
  redirectUrl: "https://..."

Flow Control

flow:
- flowActionType: skip
  value: "950000"           # skip next N rules
- flowActionType: skipAfter
  value: END-REQUEST-920    # skip to marker

Non-Disruptive Actions

These modify state or logging without stopping evaluation:

nonDisruptive:
- nonDisruptiveActionType: setvar
  value: TX.inbound_anomaly_score_pl1=+5
- nonDisruptiveActionType: msg
  value: "Attack detected on %{REQUEST_URI}"
- nonDisruptiveActionType: logdata
  value: "%{MATCHED_VAR}"
- nonDisruptiveActionType: ctl
  value: ruleRemoveById=942100
- nonDisruptiveActionType: nolog

Chaining

To express "condition A AND condition B":

- metadata: { id: 942100, ... }
  conditions: [ condition A ]
  actions:
    disruptive: { disruptiveActionType: pass }
  chainedRule: true
- metadata: { ... same id ... }
  conditions: [ condition B ]
  actions:
    disruptive: { disruptiveActionType: block }

The converter automatically emits the chain action on the first part.

SecMarker

A marker rule looks like this:

- secMarker: END-REQUEST-920-PROTOCOL-ENFORCEMENT
  metadata:
    id: 0
    phase: "1"
  conditions:
  - always-match: true
  actions:
    nonDisruptive:
    - nonDisruptiveActionType: nolog

Later rules can skipAfter: END-REQUEST-920-PROTOCOL-ENFORCEMENT.

Complete Realistic Example

See the CRS samples in the repository (config/samples/crs/) — they are the best reference for complex real-world rules including heavy use of TX variables, ctl actions, and chaining.

Conversion Guarantees

The controller uses the same crslang library that the CRS converter uses. The generated .status.secRuleString is what the WAF engine compiles (modsecurity-proxy-wasm).

If you ever want to debug "why isn't my rule matching?", copy the secRuleString value and exercise it against a local Envoy + modsecurity-proxy-wasm setup (see that module’s tests).

Full CRD Schema

For every possible field and validation rule, see the generated CRD:

Next: read the Troubleshooting page.

On this page