Skip to content

API reference

All services expose HTTP. The ext-proc service additionally exposes a gRPC endpoint.


kysira-proxy (default: :8080)

The proxy handles all traffic. Every inbound request is scored before being forwarded to the upstream application.

GET /_kysira/health

Returns the proxy health and current configuration.

Response 200 application/json

{
  "service": "kysira-proxy",
  "status": "ok",
  "mode": "shadow",
  "threshold": 0.95,
  "target": "http://localhost:3000",
  "inference": "http://localhost:8081"
}

GET /metrics

Prometheus metrics in text exposition format. Scraped by Prometheus or any compatible collector.

Response 200 text/plain

# HELP kysira_requests_total Total requests processed by action.
# TYPE kysira_requests_total counter
kysira_requests_total{action="passed"} 1024
kysira_requests_total{action="shadow_kill"} 12
kysira_requests_total{action="active_kill"} 3
kysira_requests_total{action="error"} 1
...

GET /api/mode

Returns the current operating mode.

Response 200 application/json

{"mode": "shadow"}

POST /api/mode

Switches the operating mode at runtime without a restart.

Request application/json

{"mode": "active"}

mode must be "shadow" or "active". Any other value returns 400.

Response 200 application/json

{"mode": "active"}

GET /api/events

Server-Sent Events stream of scored requests. On connect, the proxy replays the 200 most recent events before streaming live ones. A keepalive comment (: keepalive) is sent every 15 s.

Response text/event-stream

data: {"timestamp":"2025-01-01T00:00:00Z","method":"POST","path":"/rest/user/login","source_ip":"127.0.0.1","score":0.98,"reason":"sqli:union_select","mode":"shadow","action":"shadow_kill","latency_ms":41.2}

data: {"timestamp":"2025-01-01T00:00:01Z","method":"GET","path":"/","source_ip":"127.0.0.1","score":0.01,"reason":"","mode":"shadow","action":"passed","latency_ms":12.1}

: keepalive

Event fields

Field Type Description
timestamp string RFC 3339 UTC
method string HTTP method
path string Request path
source_ip string Client IP (respects CF-Connecting-IP, X-Real-IP, X-Forwarded-For)
score float Classifier score [0.0–1.0]
reason string Human-readable reason from the winning classifier
mode string Operating mode at time of request: shadow or active
action string passed, shadow_kill, or active_kill
latency_ms float Inference call latency in milliseconds

GET /api/events/recent

Returns the last 500 events as a JSON array. Useful for initial page load without subscribing to the stream.

Response 200 application/json

[
  {
    "timestamp": "2025-01-01T00:00:00Z",
    "method": "POST",
    "path": "/rest/user/login",
    "source_ip": "127.0.0.1",
    "score": 0.98,
    "reason": "sqli:union_select",
    "mode": "shadow",
    "action": "shadow_kill",
    "latency_ms": 41.2
  }
]

Events are ordered oldest-first (same order as the SSE stream).


ALL /* (catch-all)

Every other path is reverse-proxied to TARGET_URL. The proxy adds the following headers to forwarded requests:

Headers added to forwarded requests (shadow mode):

Header Value
X-Kysira-Score Classifier score, e.g. 0.9823
X-Kysira-Reason Reason string, e.g. sqli:union_select
X-Kysira-Mode Current mode: shadow or active
X-Kysira-Would-Have-Killed true — only present when score exceeds threshold in shadow mode

In active mode, requests above the score threshold receive a TCP RST and are never forwarded. The response includes X-Kysira-Killed: true and X-Kysira-Score.


kysira-inference (default: :8081)

The inference sidecar is an internal service. It is not exposed to the internet — only the proxy and ext-proc call it.

GET /health

Returns model load status.

Response 200 application/json

{
  "service": "kysira-inference",
  "status": "ok",
  "sqli_model": "/path/to/models/sqli-classifier",
  "pi_model": "/path/to/models/prompt-injection",
  "detectors": {"xss": "regex", "nosqli": "regex", "sqli": "model", "prompt-injection": "model", "...": "..."}
}

status is "loading" while models are initialising, "ok" once both are ready. detectors maps every enabled detector slug to its backing ("model" or "regex").


POST /score

Scores request text for SQL injection (primary classifier). The proxy calls this endpoint for every request.

Request application/json

{"request_text": "GET /rest/user/login?email=admin' OR '1'='1"}

request_text is a free-form string. The proxy composes it as:

{METHOD} {path}?{query}\n{body}

Response 200 application/json

{"score": 0.9921, "reason": "sqli:union_select"}
Field Type Description
score float [0.0–1.0] — higher is more suspicious
reason string Human-readable classifier reason

POST /score/xss

Scores request text for cross-site scripting using a regex-based detector.

Request same as /score

Response same schema. score is 0.95 when any XSS pattern matches, 0.0 otherwise.


POST /score/prompt-injection

Scores request text for prompt injection using the ML classifier.

Request same as /score

Response same schema.


POST /score/{detector}

Generic per-detector endpoint. {detector} is any registered detector slug (sqli, xss, prompt-injection, nosqli, command-injection, ssti, path-traversal, ldap-injection, xpath-injection, ssrf, xxe, deserialization, open-redirect, crlf). Returns 404 for an unknown slug. The legacy /score/xss and /score/prompt-injection routes above are just specific cases of this.

Request same as /score. Response same {score, reason} schema.


POST /score/all

Runs every enabled detector over the request (after a single normalization pass) and returns the highest score plus a per-detector breakdown. This is the endpoint the proxy and ext-proc now call — one round-trip replaces the old per-detector fan-out. Cheap regex detectors run first and short-circuit the expensive ML models when one already crosses the kill threshold.

Request same as /score.

Response 200 application/json

{
  "score": 0.95,
  "reason": "Detected directory traversal sequence: '../' (decoded)",
  "detector": "path-traversal",
  "breakdown": {"xss": 0.0, "path-traversal": 0.95, "sqli": 0.02, "...": 0.0}
}
Field Type Description
score float Max across all detectors
reason string Human reason from the winning detector
detector string Slug of the detector that produced the max
breakdown object {detector: score} for observability

See detection-architecture.md for the full detector catalog and the request-normalization (encoding) model.


kysira-ext-proc

The ext-proc service has two ports: HTTP (default :9090) for management, and gRPC (default :50051) for the Envoy ext_proc protocol.

HTTP port (:9090)

GET /_kysira/health

Response 200 application/json

{"service": "kysira-ext-proc", "status": "ok", "mode": "shadow"}

GET /metrics

Prometheus metrics, same format as the proxy. See deployment.md for the full metric list.


GET /api/mode

Response 200 application/json

{"mode": "shadow"}

POST /api/mode

Same contract as the proxy's /api/mode. Allows the dashboard to toggle active/shadow mode when deployed with ext_proc instead of the standalone proxy.

Request application/json

{"mode": "active"}

Response 200 application/json

{"mode": "active"}

gRPC port (:50051)

Service: envoy.service.ext_proc.v3.ExternalProcessor

Method: Process(stream ProcessingRequest) returns (stream ProcessingResponse)

This is the Envoy External Processing protocol. The ext_proc service is not called directly — Envoy calls it automatically for every request matching the EnvoyFilter workload selector.

Request header phase (ProcessingRequest.request_headers)

The service reads method, path, query string, and the end_of_stream flag.

  • If end_of_stream is true (GET, HEAD, DELETE — no body): scores immediately and either passes or blocks.
  • If end_of_stream is false (POST, PUT — body follows): sends a pass response and waits for the body.

Request body phase (ProcessingRequest.request_body)

Scores method + path + body together, then passes or blocks.

Pass responseProcessingResponse.request_headers with X-Kysira-* headers added.

Block responseProcessingResponse.immediate_response with HTTP status 403 Forbidden:

{"error": "forbidden", "reason": "<classifier reason>", "score": 0.9921}

The failure_mode_allow: true setting in the EnvoyFilter means Envoy passes the request through if the ext_proc service is unreachable or times out.