A new era of WAF.
Built for AI, built for speed.

The fastest WAF you can run in front of Kong, and the first that speaks the protocols your AI agents use. Full OWASP CoreRuleSet 4.x detection, libinjection-backed, 2 to 4 times faster than ModSecurity, with none of the Apache-era baggage.

Kong Gateway 3.x OWASP CRS 4.26 libinjection v3.10 MCP-aware
Runs on Kong Gateway OpenResty nginx Compatible with OWASP CoreRuleSet 4.x ModSecurity rules JSON-RPC / MCP
Built for the OpenResty era

Built for Kong. Not bolted on from Apache.

ModSecurity was a stroke of genius in 2002. Two decades and a whole new runtime later, half of what it does is wrestling with Apache assumptions Kong never had. Karna keeps the genius and throws out the baggage.

ModSecurity Legacy · 2002
  • Compiled as a native module; rebuild Kong / OpenResty to upgrade, downgrade, or hot-patch.
  • Anomaly-scoring engine with TX-side-effect variables: opaque, hard to tune, prone to runaway false positives.
  • Configured per-vhost in seclang. No native concept of a Kong service, route, or consumer.
  • Audit log is line-based, ModSecurity-shaped. Tooling has to reverse-engineer the format.
  • No awareness of JSON-RPC, MCP, or SSE transports, so modern agent traffic looks like an unparseable body.
  • Counter rules require SecAction initcol and an Apache-style persistence file. Not a great fit beside Redis.
Karna Modern · 2026
  • Pure Lua plugin via LuaRocks. luarocks make, kong reload, no rebuild ever.
  • Explicit eager-block + always-on validation gates. Each match carries an actionable rule id, not an opaque score.
  • First-class Kong: per-service plugin config, local rules per service, virtual patching via rule-control.
  • JSON audit log v2: one entry per request, every match in matches[]. Ship to anything that reads JSON.
  • MCP-aware out of the box. JSON-RPC envelope parsing, SSE response reassembly, per-event rule evaluation.
  • Redis-backed counters by default. No per-worker drift, no shared-dict eviction surprises.
Why we call it Karna

In the Mahabharata, Karna is the warrior born already wearing his armor. Surya, the sun god, gave his son a breastplate and earrings fused to his body, so no blade or arrow could ever reach him. That is what a WAF should be: protection your application carries from birth, not a wall bolted on after the first attack. The mark draws the Devanagari letter क (ka), the first sound of his name, with Surya's sun rising beside it.

Karna's signature move

Sanitize, don't block.

A real name like O'Brien, or an address like "Via dell'Orso, 5", trips the same SQL-injection heuristics as an attack. Every other WAF answers with a 403 and a broken signup form. Karna's fix_matched_parts action strips the dangerous characters out of the matched field and lets the request continue upstream, so a false positive costs a character, not a customer.

Works on the path, query args, headers, and body alike. Each sanitized request is logged with action: "sanitized", so you can tune from real traffic instead of guessing.

Other WAFs● 403 blocked
POST /signup name=O'Brien
Legitimate customer bounced. The form looks broken, the support ticket is on its way.
Karna · fix_matched_parts● 200 sanitized
POST /signup name=OBrien # quote removed, request flows
Upstream never sees the unsafe character. Audit log records action: "sanitized".
Benchmarks · Hetzner CCX, 2 vCPU · k6 at 20 VU

Security at full throttle.

Same hardware, same OWASP CRS, the same HTTP status on every request. So this is pure throughput, not leniency. Karna doesn't just keep pace with ModSecurity, it laps it, and it's a Lua plugin doing the lapping.

Requests / second (higher is better) Apache + ModSec2 nginx + ModSec3 Caddy + Coraza Karna
Blocking attacks85216235703326
Mixed real-world traffic61212703371569
API with embedded attacks190688184815
Benign throughput, no cache39211393191310
2 to 4 times faster than ModSecurity, 2 to 11 times faster than Coraza. The one workload it trails is multipart uploads, where nginx's native C++ body parser edges it by 7%. The charts, every scenario, and the full methodology live on the benchmark page.
See the full benchmark
CRS regression suite · CRS 4.26.0

All of CRS. None of the false-positive pain.

CRS is brilliant detection wrapped in rules that bury teams in false positives. Karna ships the operational layer it's missing: the in-repo CRS-fix controls switch off the worst offenders for you, automatically. What's left passes the entire in-scope suite, clean in both directions. No missed attacks, no false alarms.

100%
PL1 · pass rate
2757 / 2757 tests · recommended posture
100%
PL2 · pass rate
4071 / 4071 tests
99.9%
PL3 · pass rate
4604 / 4608 tests
99.9%
PL4 · pass rate
4670 / 4674 tests
Not vendor numbers: run the same harness yourself from crs-regression-test/. Every rule Karna intentionally doesn't fire is documented, and the handful of PL3+ residuals are listed. Anything failing that isn't on the list is a real gap, please open an issue.
Capabilities

Everything you expect from a WAF, and the parts you've been missing.

The headline acts are above. Below is the rest of what comes in the box, and it's a lot. Every knob and variable is in the README.

OWASP CoreRuleSet 4.x

Full CRS loader at init_worker, tracking 4.26.0 in the regression suite. Seclang operators (@detectSQLi, @streq, @ipMatch) map to engine-native names automatically.

CRS 4.x · paranoia 1 to 4

libinjection via FFI

SQLi and XSS detection straight from the C library, called via LuaJIT FFI. No regex roulette for the two attack classes regex handles worst.

libinjection v3.10 · zero-copy

Always-on validation gates

Method allow-list, path-character policy, header deny-list, content-type and charset allow-list. The structural checks run before any rule: fast, deterministic, no false positives.

4 gates · pre-rule

Local rules & virtual patching

Per-service rules_request / rules_response as stringified JSON. Adjust, exclude, or rewrite CRS rules at request time through the rule-control layer, without forking the CoreRuleSet.

rule-control · per service

Action & response overrides

Change what an existing rule does from plugin config: switch a block to sanitize, drop a terminal action, force a block, or rewrite the response. Selected by id, range, or tag. The cached rule pack is never mutated.

rule_action_overrides · rule_response_overrides

CRS exclusion plugins

Load the upstream CRS app plugins (WordPress, Drupal, and friends) straight from disk, or inline your own SecLang. Their ctl:ruleRemove* directives are parsed and applied per request.

crs_plugins_enabled · from disk

Every body format, flattened

URL-encoded, JSON, multipart, and XML bodies parsed and flattened to key-value pairs for rule evaluation. Optional base64 and gzip decode. The multipart parser is hardened against known smuggling bypasses.

urlencoded · json · multipart · xml

Redis rate-limit & counters

Native fixed-window rate limiting as a rule action, plus standalone counter increments, both backed by Redis. No shared-dict eviction games, no per-worker drift. Slots next to your existing Kong Redis.

rate_limit · redis_incr_key

Audit log v2, with custom fields

One JSON entry per request, every match in matches[]. Add your own fields with set_log_fields. Ship to Loki, OpenSearch, or S3. A ModSecurity-compatible v1 still ships for replay parity.

JSON v2 · set_log_fields

Request enrichment

GeoIP, ASN, and user-agent data set by sibling Kong plugins is picked up as geoip.* / asn.* rule variables and recorded in the audit log. Sibling plugins can also append their own external_matches.

geoip · asn · user-agent

Plays well with other plugins

A Karna detection can write into kong.ctx.shared with set_variable, so a sibling plugin downstream changes its behaviour without ever knowing Karna is there. The sidecar pattern, done cleanly.

set_variable · kong.ctx.shared

Detection & blocking modes

Toggle engine_blocking_mode per service. Shadow-deploy alongside the existing edge, compare audit logs, then flip to enforce once the signal is clean.

detectionblocking

22+ engine operators

rx, libinjection_sqli, libinjection_xss, ipMatch, pmFromFile, validateUtf8Encoding, validateByteRange, in positive and negated forms. Dispatched by string equality, no surprises.

seclang-compatible

Install it into your AI agent

Karna ships a Claude Code skill that teaches your coding agent how to deploy, configure, and write rules for the WAF. One line to install, then just ask. Install the skill →

Claude Code skill · one-line install
The rule pipeline

Layered, ordered, and easy to reason about.

Every request flows through the same four layers, in the same order. No anomaly scoring, no implicit chains across phases. Each layer is independently toggleable per service.

Ingress
HTTP request
Kong access
phase
1

Always-on validation gates

Method allow-list · path-character policy · header deny-list · content-type / charset allow-list. Runs first, unconditionally: fail closed, fast.

Always on
2

Per-service rule controls

Adjust, exclude, or rewrite global rules at request time. Includes the in-repo CRS-fix layer that neutralises known false-positive-prone OWASP CRS rules in production.

rule-control
3

Per-service local rules

Your custom rules_request / rules_response. Virtual patching for emergencies that don't deserve a deploy.

local_rules_enabled
4

OWASP CoreRuleSet

The CRS rule pack loaded from disk at init_worker. Paranoia 1 to 4. Detection-only or blocking per service.

coreruleset_enabled
to upstream
or 403
Verdict
audit log v2
Detection-only or blocking is one flag: engine_blocking_mode. Default is detection-only, so you can shadow-deploy with confidence.
For agent traffic

The first WAF that speaks MCP.

Karna parses the JSON-RPC envelope on every request, reassembles SSE responses on the Streamable HTTP transport, and runs rules per event. Your model gateway gets the same layered defense as the rest of the API surface.

  • JSON-RPC 2.0 envelope validation via mcp_jsonrpc_valid
  • Method allow-list via mcp_method_in
  • Per-event evaluation on SSE response streams
  • Rule variables in the mcp.* namespace, plus request.body.json.*
audit · matches[0] ● blocked
{
  "rule_id":    "mcp_method_denied",
  "phase":      "request",
  "transport":  "streamable-http",
  "method":     "tools/call",
  "variable":   "request.body.json.params.name",
  "matched":    "shell_exec",
  "action":     {
    "fixed_response": 403,
    "reason":         "tool not allow-listed"
  },
  // karna.engine 1.0.0 · paranoia=2 · 1.8ms
}
Install with Docker

The whole WAF in one Docker image.

Kong, the OWASP CoreRuleSet, libinjection, and Karna baked together. Run it as a reverse proxy in front of any app you already have, in any language, then flip blocking on with one flag.

1

Clone and build

The production Dockerfile ships in the repo. git clone, then docker build gives you one self-contained image: Kong, the OWASP CoreRuleSet, libinjection, and Karna baked together. Nothing to compile by hand.

2

Point it at your app

Karna runs on Kong as a reverse proxy, so it sits in front of whatever you already run: any language, any framework, on any host. A small DB-less kong.yml routes traffic to your backend and turns Karna on.

3

Run it

One docker compose brings up Karna in front of your app (and Redis for rate-limiting), and traffic flows client → Karna → your app. Start in detection-only, watch the audit log, then set engine_blocking_mode to block.

# clone the repo (the production Dockerfile ships with it)
git clone https://github.com/sicuranext/karna
cd karna

# one self-contained image: Kong + OWASP CRS + libinjection + Karna,
# plus the native RE2 / Aho-Corasick scanners. Nothing to compile by hand.
docker build -f docker/Dockerfile -t karna .
# kong.yml · DB-less. Put Karna in front of any backend.
_format_version: "3.0"
services:
  - name: my-app
    url: http://my-backend:8080   # your existing app, any language
    routes:
      - name: my-app
        paths: ["/"]
    plugins:
      - name: karna
        config:
          engine_blocking_mode: false  # start detect-only; set true to block (403)
          paranoia_level: 1          # OWASP CRS paranoia 1-4
          auditlog_enabled: true
          redis_host: redis          # only used by rate_limit rules
          redis_port: 6379
# point kong.yml at your app, then bring up Karna + Redis (DB-less)
docker compose -f docker/docker-compose.prod.yml up -d

# …or run the image on its own (rate_limit rules then need an external Redis)
docker run -d --name karna -p 8000:8000 \
  -e KONG_DATABASE=off \
  -e KONG_DECLARATIVE_CONFIG=/kong/kong.yml \
  -v $PWD/docker/kong.yml:/kong/kong.yml:ro \
  karna

# traffic now: client -> :8000 (Karna / Kong) -> my-backend:8080
Already running Kong?

Drop Karna into the Kong you already run.

Karna is a pure Lua plugin, so it installs into an existing Kong or OpenResty without rebuilding anything. luarocks make, enable it in kong.conf, reload. Add libinjection and the CoreRuleSet once and you are inspecting live traffic.

1

Install the plugin

Clone the repo and run ./scripts/install.sh. One command installs the plugin (pure Lua, no rebuild of Kong or OpenResty), builds libinjection, downloads the OWASP CoreRuleSet, and builds the native scanners for the fast path.

2

Enable it in kong.conf

Add karna to plugins, set the PCRE match limit, and kong reload. The plugin loads without a restart.

3

Turn it on per service

Attach Karna to any service or route through the Admin API or your declarative config. Start in detection-only, then flip engine_blocking_mode on.

# one command: the plugin + libinjection + the OWASP CoreRuleSet
# + the native RE2 / Aho-Corasick scanners. On a Debian-based Kong
# image it installs the build dependencies too.
git clone https://github.com/sicuranext/karna.git
cd karna
sudo ./scripts/install.sh

# override a default, or skip a piece you already have:
#   CRS_VERSION=4.27.0 sudo -E ./scripts/install.sh
#   sudo ./scripts/install.sh --skip-libinjection --skip-crs
# kong.conf · enable Karna, no rebuild
plugins = bundled,karna

# cap PCRE backtracking (safety vs catastrophic regex)
nginx_http_lua_regex_match_limit = 100000

# then reload Kong to load the plugin (no restart needed)
kong reload
# turn Karna on for an existing service (Admin API)
curl -X POST http://localhost:8001/services/<service>/plugins \
  -d name=karna \
  -d config.engine_blocking_mode=false \
  -d config.paranoia_level=1 \
  -d config.auditlog_enabled=true

# start detect-only; set engine_blocking_mode=true to block (403)
Source-available · Elastic License 2.0

Put Karna in front of whatever you already run.

You don't need a Kong-native stack. Karna runs on Kong as a reverse proxy, so it guards any app behind it: any language, any framework, anything already in production. Source-available, and the same crs-regression-test/ harness that gates every release ships in the repo.