Security Rules
Overview
Security Rules are ordered, first-match-wins rules that evaluate the coreX expression language against request properties and execute an action. They are the primary access control and traffic shaping mechanism in coreX Manager, sitting between WAF evaluation and backend routing.
Each rule has:
- A name for identification
- An expression in the coreX expression language
- An action to execute when the expression matches
- A priority (drag-and-drop ordering)
- Listener scoping (which listeners the rule applies to)
The coreX Expression Language
The expression language is coreX Manager’s own DSL for matching request properties. It supports field access, comparison operators, logical operators, and list membership.
Fields
| Field | Description |
|---|---|
http.request.method | HTTP method (GET, POST, etc.) |
http.request.uri | Full request URI (path + query) |
http.request.uri.path | Request path only |
http.request.uri.query | Query string |
http.request.headers["name"] | Request header value |
http.request.host | Host header |
http.request.user_agent | User-Agent header |
http.request.content_type | Content-Type header |
http.request.body | Request body (when buffered) |
http.request.cookies["name"] | Cookie value |
ip.src | Client IP address |
ip.geoip.country | Client GeoIP country code |
ip.geoip.city | Client GeoIP city |
ip.geoip.asn | Client ASN |
tls.ja4 | JA4 TLS fingerprint |
tls.version | TLS version |
tls.cipher | Negotiated cipher |
auth.valid | Whether auth validation passed |
auth.type | Detected auth type (jwt, api_key, bearer) |
risk.score | Risk score (0-99) from Risk Scoring |
risk.rules_hit | Names of matched risk rules |
risk.rules_hit_count | Count of matched risk rules |
risk.hit_density | Risk hit density percentage |
risk.<slug>.score | Per-ruleset risk score |
waf.anomaly_score | WAF anomaly score |
waf.action | WAF action taken |
rate_limit.exceeded | Whether rate limit was exceeded |
Operators
| Operator | Description |
|---|---|
== | Equal |
!= | Not equal |
> / >= | Greater than / greater or equal |
< / <= | Less than / less or equal |
in | Membership in a Security List |
not in | Not in a Security List |
contains | String contains substring |
matches | Regex match |
and | Logical AND |
or | Logical OR |
not | Logical NOT |
Examples
ip.geoip.country in $geo:high_risk_countries
http.request.uri.path matches "^/admin"
http.request.method == "POST" and http.request.content_type contains "json"
ip.src in $network:blocked_networks
risk.score > 50 and not auth.valid
tls.ja4 in $ja4:bad_fingerprints
http.request.headers["x-api-key"] == ""
Security Lists are referenced with the $type:name syntax (e.g. $geo:high_risk_countries, $network:blocked_networks). The list must exist before the rule can be applied.
Actions
| Action | Description |
|---|---|
| allow | Permit the request through |
| deny | Block with 403 |
| challenge | Present a CAPTCHA challenge |
| log | Log the request but allow it through |
| tarpit | Block and hold the connection (block duration) |
| skip_rules | Skip subsequent security rules |
| skip_ratelimit | Skip rate limiting for this request |
| skip_waf | Skip WAF evaluation for this request |
Rule Ordering
Rules are evaluated in priority order (drag-and-drop). The first rule whose expression matches determines the action. If no rules match, the request is allowed through to backend routing.
Only the first matching rule’s action is executed. Place more specific rules above more general ones. For example, a rule that allows a specific IP should come before a rule that blocks a broader range containing that IP.
Listener Scoping
Each rule can be scoped to specific listeners:
| Scope | Description |
|---|---|
| All listeners | Rule applies to every listener (default) |
| Specific listeners | Rule applies only to selected listener IDs |
| Frontend match | Rule applies to listeners matching a frontend name pattern |
Step-by-Step: Block a Country
-
Create a GeoIP Security List:
- Navigate to Security > Security Lists > GeoIP tab
- Add a list named
blocked-countries - Add country codes (e.g.
XX,YY) - Save
-
Create the Security Rule:
- Navigate to Security > Security Rules
- Click Add Rule
- Name:
block-high-risk-countries - Expression:
ip.geoip.country in $geo:blocked_countries - Action:
deny - Scope: All listeners (or specific)
- Save
-
Apply:
- Click Apply Changes
Step-by-Step: Challenge High-Risk Requests
-
Ensure Risk Scoring is configured (see Risk Scoring)
-
Navigate to Security > Security Rules
-
Click Add Rule
-
Name:
challenge-high-risk -
Expression:
risk.score > 50 -
Action:
challenge -
Save and Apply
Use and not auth.valid to only challenge unauthenticated requests: risk.score > 50 and not auth.valid. This avoids challenging known-good API clients.
Step-by-Step: Skip WAF for a Trusted Path
- Navigate to Security > Security Rules
- Click Add Rule
- Name:
skip-waf-webhooks - Expression:
http.request.uri.path matches "^/webhooks/" and http.request.headers["x-webhook-secret"] == "my-secret" - Action:
skip_waf - Save and Apply
Verification
- Preview the config — on the Dashboard, click Preview and search for your rule name in the generated HAProxy config
- Test with a matching request:
curl -k https://localhost/ -H "X-Test: match" - Test with a non-matching request:
curl -k https://localhost/ - Check access logs — navigate to Observability > Access Logs and verify the security rule action is logged
Next Steps
- Security Lists — Create reusable IP, ASN, GeoIP, and JA4 lists
- Risk Scoring — Score requests and reference scores in rules
- WAF — Layer Coraza WAF rules
- CAPTCHA — Configure challenge providers