Docs / Configuration
Reference

Configuration options

Every setting in the Karna plugin schema. Set them in the plugin config block (declarative kong.yml) or via the Admin API. All options are per-service: the same Karna can run in detection-only on one route and blocking on another.

Engine & blocking

The core switches: whether Karna blocks or only detects, and which rule sources are active.

OptionType · defaultDescription
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)CRSDefaultCategory
method_enforcement911trueMethod enforcement
scanner_detection913trueScanner / bot detection
protocol_enforcement920falseHTTP 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_attack921trueProtocol attacks (request smuggling, splitting)
multipart_attack922trueMultipart/form-data attacks
lfi930trueLocal file inclusion
rfi931trueRemote file inclusion
rce932trueRemote command execution
php933truePHP injection
generic934trueGeneric / node / SSRF
xss941trueCross-site scripting
sqli942trueSQL injection
session_fixation943trueSession fixation
java944trueJava attacks
NoteOnly the request-side attack categories are exposed. Anomaly scoring (949/959/980), response rules (95x), init (901), and the common-exception files (905/999, which other rules depend on for false-positive handling) are not toggleable.

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.

OptionType · defaultDescription
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.
NoteThree further engine optimizations are unconditional (no toggle) because each is proven detection-neutral by a CRS regression empty-diff: skip body-requiring rules on a bodyless request, per-request transform-cache hoist, and skip multipart-namespace scans on non-multipart bodies.

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.

OptionType · defaultDescription
request_methods_allowedarray gateAllowed HTTP methods. Default: GET, HEAD, PUT, POST, DELETE, OPTIONS, PATCH, PROPFIND. A request with any other method is rejected.
check_special_chars_in_pathboolean trueEnforce a cap on special characters in the URL path.
limit_special_chars_in_pathnumber 3Maximum special characters allowed in the path when the check is on.
check_invalid_chars_in_pathboolean falseEnforce a cap on invalid characters in the URL path.
limit_invalid_chars_in_pathnumber 1Maximum invalid characters allowed in the path when the check is on.
request_headers_deniedarray gateHeader names that cause rejection if present. Default: content-encoding, proxy, lock-token, content-range, if.
request_content_type_allowedarray gateAllowed request Content-Type values (urlencoded, multipart, the XML family, JSON and CloudEvents variants by default).
request_content_type_charset_allowedarray gateAllowed charsets. Default: utf-8, iso-8859-1, iso-8859-15, windows-1252.
limit_arg_numnumber 255Maximum number of arguments (query + body). Enforced as a gate before the rule loop — protects against a (rules × args) blow-up DoS.
limit_arg_name_lengthnumber 100Maximum length of a single argument name.
limit_arg_value_lengthnumber 400Maximum length of a single argument value.
total_arg_value_lengthnumber 64000Maximum combined length of all argument values.
restricted_extensionsarrayFile 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_ipsboolean falseWhen true, skip the WAF for requests from loopback / RFC1918 ranges. Default false: everything is inspected.
GotchaDefault is 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.

OptionType · defaultDescription
paranoia_levelnumber 1OWASP CRS paranoia level (1–4). Higher levels add stricter rules and more potential false positives.
validate_utf8_encodingboolean trueMaps 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

OptionType · defaultDescription
try_bas64decode_if_possibleboolean falseAttempt to base64-decode values during body parsing so an attacker cannot hide a payload behind base64.
inspection_table_convertarrayAdvanced. 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.

OptionType · defaultDescription
mcp_enabledboolean falseTurn on MCP parsing and the mcp.* variable namespace for the route.
mcp_routesarray []Route paths treated as MCP endpoints.
mcp_detection_heuristicboolean falseHeuristically detect MCP traffic even on non-listed routes.
mcp_protocol_versions_allowedarrayAccepted MCP protocol versions. Default: 2025-11-25, 2025-06-18, 2025-03-26.
mcp_block_legacy_sse_transportboolean falseReject the legacy HTTP+SSE transport, allowing only Streamable HTTP.
mcp_origin_check_enabledboolean trueValidate the Origin header against mcp_origins_allowed (DNS-rebinding defense).
mcp_origins_allowedarray []Allowed origins when the origin check is on.
mcp_max_event_size_bytesnumber 1048576Maximum size of a single reassembled SSE event (1 MiB).
mcp_max_stream_buffer_bytesnumber 8388608Maximum SSE reassembly buffer per stream (8 MiB).
mcp_redact_session_id_in_auditboolean trueRedact the MCP session id in audit logs.
mcp_redact_authorization_in_auditboolean trueRedact 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.

OptionType · defaultDescription
crs_plugins_pathstring /opt/coreruleset-plugins/Directory containing the cloned CRS plugin repos.
crs_plugins_enabledarray []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.

OptionType · defaultDescription
rules_requestarrayPer-service request-phase rules, each a JSON string in Karna's rule format.
rules_responsearrayPer-service response-phase rules (header / body filter), each a JSON string.
custom_secrulesarray []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.

OptionType · defaultDescription
rule_action_overridesarray []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_overridesarray []Customise the block response: status_code, body (supports %{var} macros), and merged headers.
rule_action_overrides — entryjson
{
  "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.

OptionType · defaultDescription
auditlog_enabledboolean trueWrite audit logs.
auditlog_pathstring /usr/local/openresty/nginx/logsDirectory for the JSON log files. Must be chowned to the Kong worker user.
auditlog_formatstring v2One 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_matchboolean falseOnly write a log entry when at least one rule matched.
auditlog_modsecboolean falseEmit the ModSecurity-compatible audit format (with v1).
auditlog_error_log_on_matchboolean falseAlso 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.

OptionType · defaultDescription
redis_hoststring localhostRedis host.
redis_portnumber 6379Redis port.
redis_passwordstringRedis password (optional).
redis_databasenumber 0Database index. A SELECT is issued only when this is greater than 0.
redis_inspect_enabledboolean falseMaster 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_msnumber 50Connect / send / read timeout for inspection reads, in milliseconds. Kept short so a slow Redis cannot stall the request path.
redis_keepalive_pool_sizenumber 64Connection pool size for the inspection client.
redis_keepalive_idle_msnumber 60000How long an idle pooled connection is kept, in milliseconds.
redis_on_errorstring skipWhat 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.
Read-only boundaryThe inspection client enforces a deny-by-default command whitelist (only read commands such as 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

OptionType · defaultDescription
private_debugboolean falseVerbose 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.

probesh
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.

VariableDefaultPurpose
KARNA_CRS_PATH/opt/coreruleset/rules/Path to the OWASP CoreRuleSet rules/ directory (trailing slash auto-normalized).
KARNA_LIBINJECTION_SO/usr/local/lib/libinjection.soPath to the compiled libinjection shared library.
KARNA_LIBKA_RE2_SO/usr/local/lib/libka_re2.soPath to the RE2 scanner. Missing → pure-Lua @rx fallback.
KARNA_LIBKA_AC_SO/usr/local/lib/libka_ac.soPath to the Aho-Corasick scanner. Missing → pure-Lua @pm fallback.
KARNA_PROFILEunsetIf set, enables LuaJIT profiling. Diagnostics only.
main-env.confnginx
# 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;