Engine & blocking
The core switches: whether Karna blocks or only detects, and which rule sources are active.
| Option | Type · default | Description |
|---|---|---|
engine_blocking_mode |
boolean false | When true, a matched rule terminates the request (default 403). When false (detection-only), rules are evaluated and logged but never block. Always start here. |
coreruleset_enabled |
boolean true | Load and evaluate the OWASP CoreRuleSet pack (loaded once at worker start, shared across requests). The in-repo CRS-fix rule controls are always applied independently of this toggle. |
local_rules_enabled |
boolean true | Evaluate your per-service custom rules (rules_request / rules_response). |
set_karna_headers |
boolean false | Add Karna diagnostic headers to the response. Useful when debugging behind a proxy where worker logs are not visible. |
CRS category toggles
The full CoreRuleSet is loaded once; these per-service switches decide which attack categories are evaluated for this service. A disabled category's rules are skipped in the eval loop (no per-request cost). All are gated by coreruleset_enabled and live under the coreruleset_rulesets record. Each defaults true except where noted.
Option (under coreruleset_rulesets) | CRS | Default | Category |
|---|---|---|---|
method_enforcement | 911 | true | Method enforcement |
scanner_detection | 913 | true | Scanner / bot detection |
protocol_enforcement | 920 | false | HTTP protocol enforcement. Default off: nginx/OpenResty already enforces method/version/header/encoding well-formedness before the request reaches the rule engine, so this pack is largely redundant in a Kong deployment. |
protocol_attack | 921 | true | Protocol attacks (request smuggling, splitting) |
multipart_attack | 922 | true | Multipart/form-data attacks |
lfi | 930 | true | Local file inclusion |
rfi | 931 | true | Remote file inclusion |
rce | 932 | true | Remote command execution |
php | 933 | true | PHP injection |
generic | 934 | true | Generic / node / SSRF |
xss | 941 | true | Cross-site scripting |
sqli | 942 | true | SQL injection |
session_fixation | 943 | true | Session fixation |
java | 944 | true | Java attacks |
Engine optimizations
Performance switches. All default on and are detection-neutral. Each degrades gracefully: if a native library is missing, the engine falls back to the pure-Lua path and never silently drops a check.
| Option | Type · default | Description |
|---|---|---|
engine_re2_match |
boolean true | Run the @rx operator's match via RE2 (linear-time, ReDoS-safe by construction) instead of PCRE. Patterns RE2 rejects (lookaround/backref) fall back to ngx.re. Needs libka_re2.so. |
engine_re2_scan |
boolean true | Gate per-rule @rx evaluation with a single RE2::Set scan per request value; rules whose @rx matched nothing are skipped. Roughly 2× benign throughput. Needs libka_re2.so. |
engine_ac_pm |
boolean true | Replace the Lua @pm / @pmFromFile keyword loops with a C Aho-Corasick one-pass match. Needs libka_ac.so. |
engine_fast_path |
boolean true | Skip the per-rule ARGS deep-copy when no rule_control mutation is pending. |
Validation gates & limits
Request-shape limits. Four of these run as always-on gates before the rule loops and fire regardless of engine_blocking_mode and the CRS toggles: allowed methods, path character limits, denied headers, and content-type/charset. Treat them as hard limits — to loosen, raise the value or extend the allow-list.
| Option | Type · default | Description |
|---|---|---|
request_methods_allowed | array gate | Allowed HTTP methods. Default: GET, HEAD, PUT, POST, DELETE, OPTIONS, PATCH, PROPFIND. A request with any other method is rejected. |
check_special_chars_in_path | boolean true | Enforce a cap on special characters in the URL path. |
limit_special_chars_in_path | number 3 | Maximum special characters allowed in the path when the check is on. |
check_invalid_chars_in_path | boolean false | Enforce a cap on invalid characters in the URL path. |
limit_invalid_chars_in_path | number 1 | Maximum invalid characters allowed in the path when the check is on. |
request_headers_denied | array gate | Header names that cause rejection if present. Default: content-encoding, proxy, lock-token, content-range, if. |
request_content_type_allowed | array gate | Allowed request Content-Type values (urlencoded, multipart, the XML family, JSON and CloudEvents variants by default). |
request_content_type_charset_allowed | array gate | Allowed charsets. Default: utf-8, iso-8859-1, iso-8859-15, windows-1252. |
limit_arg_num | number 255 | Maximum number of arguments (query + body). Enforced as a gate before the rule loop — protects against a (rules × args) blow-up DoS. |
limit_arg_name_length | number 100 | Maximum length of a single argument name. |
limit_arg_value_length | number 400 | Maximum length of a single argument value. |
total_arg_value_length | number 64000 | Maximum combined length of all argument values. |
restricted_extensions | array | File extensions blocked in the URL path (secret keys, configs, backups, scripts, …). Aligned with the CRS 4.x tx.restricted_extensions default set. Shrink it per deployment if too strict. |
ignore_from_local_ips | boolean false | When true, skip the WAF for requests from loopback / RFC1918 ranges. Default false: everything is inspected. |
false, so local/private-range traffic is inspected like everything else. If you set it to true, requests from localhost and RFC1918 ranges — including a load balancer's private egress IP — are skipped entirely. Convenient for trusted internal traffic, but then a local-sourced attack will look like it "isn't being blocked".CRS-setup knobs
CRS-setup-style values, mapped at access-phase start into the tx.* variables CRS rules expect, so rules written against ModSecurity's TX:<name> resolve without a crs-setup.conf.
| Option | Type · default | Description |
|---|---|---|
paranoia_level | number 1 | OWASP CRS paranoia level (1–4). Higher levels add stricter rules and more potential false positives. |
validate_utf8_encoding | boolean true | Maps to TX:CRS_VALIDATE_UTF8_ENCODING so the CRS UTF-8 validation rules (e.g. 920250) behave correctly. Defaults to the CRS strict posture. |
Body parsing
| Option | Type · default | Description |
|---|---|---|
try_bas64decode_if_possible | boolean false | Attempt to base64-decode values during body parsing so an attacker cannot hide a payload behind base64. |
inspection_table_convert | array | Advanced. Additional value namespaces to convert into the inspection table for rule evaluation. Rarely needed; leave unset unless you know you need it. |
MCP (AI / Model Context Protocol)
Request- and response-side detection for the Model Context Protocol. Everything is off by default; Karna behaves identically to non-MCP traffic until mcp_enabled is set on a route.
| Option | Type · default | Description |
|---|---|---|
mcp_enabled | boolean false | Turn on MCP parsing and the mcp.* variable namespace for the route. |
mcp_routes | array [] | Route paths treated as MCP endpoints. |
mcp_detection_heuristic | boolean false | Heuristically detect MCP traffic even on non-listed routes. |
mcp_protocol_versions_allowed | array | Accepted MCP protocol versions. Default: 2025-11-25, 2025-06-18, 2025-03-26. |
mcp_block_legacy_sse_transport | boolean false | Reject the legacy HTTP+SSE transport, allowing only Streamable HTTP. |
mcp_origin_check_enabled | boolean true | Validate the Origin header against mcp_origins_allowed (DNS-rebinding defense). |
mcp_origins_allowed | array [] | Allowed origins when the origin check is on. |
mcp_max_event_size_bytes | number 1048576 | Maximum size of a single reassembled SSE event (1 MiB). |
mcp_max_stream_buffer_bytes | number 8388608 | Maximum SSE reassembly buffer per stream (8 MiB). |
mcp_redact_session_id_in_audit | boolean true | Redact the MCP session id in audit logs. |
mcp_redact_authorization_in_audit | boolean true | Redact the Authorization header in audit logs. |
CRS exclusion plugins
The OWASP project ships rule-exclusion plugins for specific apps (WordPress, Drupal, Nextcloud, phpBB, …). Karna reuses those upstream plugin repos verbatim — clone them into crs_plugins_path and list the ones to load.
| Option | Type · default | Description |
|---|---|---|
crs_plugins_path | string /opt/coreruleset-plugins/ | Directory containing the cloned CRS plugin repos. |
crs_plugins_enabled | array [] | Plugin directory names to load (e.g. wordpress-rule-exclusions-plugin). The .conf files under <path>/<name>/plugins/ are parsed alongside the CRS pack. |
Custom rules
Three ways to add your own detection. See Writing rules for the full format.
| Option | Type · default | Description |
|---|---|---|
rules_request | array | Per-service request-phase rules, each a JSON string in Karna's rule format. |
rules_response | array | Per-service response-phase rules (header / body filter), each a JSON string. |
custom_secrules | array [] | Inline SecLang rule strings (ModSecurity syntax). Parsed at worker start and added to the global pool. Good for one-off exclusions without dropping a .conf on disk. |
Action & response overrides
Change what an existing rule does at match time without editing the rule pack. Each entry is a JSON string with a selector (any of ids, id_ranges, tags, except_ids, except_tags, any) and a payload. First matching entry wins; the cached rule pack is never mutated.
| Option | Type · default | Description |
|---|---|---|
rule_action_overrides | array [] | Change what a rule does. Action types: fix (switch to sanitize, with remove_chars_pattern), passthrough (drop the terminal action), block (force a block). |
rule_response_overrides | array [] | Customise the block response: status_code, body (supports %{var} macros), and merged headers. |
{
"selector": { "tags": ["attack-xss"] },
"action": { "type": "fix", "remove_chars_pattern": "[<>\"'&;]" }
}Audit logging
Karna writes JSON audit logs to disk asynchronously. The log directory must be writable by the Kong worker user.
| Option | Type · default | Description |
|---|---|---|
auditlog_enabled | boolean true | Write audit logs. |
auditlog_path | string /usr/local/openresty/nginx/logs | Directory for the JSON log files. Must be chowned to the Kong worker user. |
auditlog_format | string v2 | One of v1 / v2. v2 writes one entry per request with all matches in a matches array; v1 is last-match-wins (ModSecurity-compatible when auditlog_modsec is on). |
auditlog_only_on_match | boolean false | Only write a log entry when at least one rule matched. |
auditlog_modsec | boolean false | Emit the ModSecurity-compatible audit format (with v1). |
auditlog_error_log_on_match | boolean false | Also emit a line to the nginx error log on a match. |
Redis
Redis is optional. It backs the rate_limit and redis_incr_key rule actions, the redis.<key> inspection variables, the redis_sismember / redis_hexists operators, and the redis_set / redis_sadd / redis_del write actions. The rest of the WAF works without it. With a shared Redis, these turn per-request, single-node state into cluster-wide state: a ban written on one node is seen by every node.
| Option | Type · default | Description |
|---|---|---|
redis_host | string localhost | Redis host. |
redis_port | number 6379 | Redis port. |
redis_password | string | Redis password (optional). |
redis_database | number 0 | Database index. A SELECT is issued only when this is greater than 0. |
redis_inspect_enabled | boolean false | Master switch for the redis.<key> inspection variables and the redis_sismember / redis_hexists operators. Off by default so a rule never opens a Redis connection you did not ask for. The write actions and rate_limit / redis_incr_key are not gated by this. |
redis_timeout_ms | number 50 | Connect / send / read timeout for inspection reads, in milliseconds. Kept short so a slow Redis cannot stall the request path. |
redis_keepalive_pool_size | number 64 | Connection pool size for the inspection client. |
redis_keepalive_idle_ms | number 60000 | How long an idle pooled connection is kept, in milliseconds. |
redis_on_error | string skip | What an inspection read does when Redis is unreachable or errors: skip (condition does not match, traffic flows), fail_open (same effect, explicit), or fail_closed (treat as a match — deny when shared state cannot be read). Default skip keeps a Redis outage from blocking traffic. |
GET, EXISTS, SISMEMBER, HEXISTS, TTL). A rule can never run a write, admin, scripting, or key-scan command through an inspection variable. Mutations go only through the dedicated write actions.Debug
| Option | Type · default | Description |
|---|---|---|
private_debug | boolean false | Verbose internal debug output. Off in production. |
Identification endpoint
Karna answers a reserved path so you can confirm it is running in front of a service and read its build (version + git commit). It is always on — there is no config flag — and the reserved path never reaches the upstream.
curl -s https://your-host/.well-known/karna
# {"engine":"karna","version":"1.0.0","commit":"<sha>","commit_short":"<short>","built_at":"<iso8601>"}The same version and commit appear in the engine block of every audit-log v2 entry. The commit is stamped at build time (the Docker image via a build arg, source installs via scripts/install.sh); an unstamped build reports commit: "unknown".
Environment variables
A few paths are read once at worker start via os.getenv(). Because nginx wipes the environment by default, any variable you set must be declared in the nginx main context with an env NAME; directive — point KONG_NGINX_MAIN_INCLUDE at a snippet like docker/main-env.conf.
| Variable | Default | Purpose |
|---|---|---|
KARNA_CRS_PATH | /opt/coreruleset/rules/ | Path to the OWASP CoreRuleSet rules/ directory (trailing slash auto-normalized). |
KARNA_LIBINJECTION_SO | /usr/local/lib/libinjection.so | Path to the compiled libinjection shared library. |
KARNA_LIBKA_RE2_SO | /usr/local/lib/libka_re2.so | Path to the RE2 scanner. Missing → pure-Lua @rx fallback. |
KARNA_LIBKA_AC_SO | /usr/local/lib/libka_ac.so | Path to the Aho-Corasick scanner. Missing → pure-Lua @pm fallback. |
KARNA_PROFILE | unset | If set, enables LuaJIT profiling. Diagnostics only. |
# included via KONG_NGINX_MAIN_INCLUDE so workers can see these
env KARNA_CRS_PATH;
env KARNA_LIBINJECTION_SO;
env KARNA_LIBKA_RE2_SO;
env KARNA_PROFILE;