Skip to content

Deployment guide

Kysira ships in two deployment models. Choose the one that fits your infrastructure.


Model 1 — Standalone proxy

The Go proxy sits in front of your application and reverse-proxies all traffic. No changes to your app or infrastructure are required.

Client → kysira-proxy :8080 → Your App
          kysira-inference :8081

The proxy adds X-Kysira-Score, X-Kysira-Reason, and X-Kysira-Mode headers to every forwarded request so your app can log or react to threat scores if it wants. In shadow mode, X-Kysira-Would-Have-Killed: true is added when a request exceeds the score threshold.

Helm install

helm install kysira oci://ghcr.io/kysira/charts/kysira-platform \
  --namespace kysira --create-namespace \
  --set "kysira-proxy.config.targetURL=http://your-app:3000"

Or install the proxy chart alone (requires a separately running inference service):

helm install kysira-proxy oci://ghcr.io/kysira/charts/kysira-proxy \
  --namespace kysira \
  --set config.targetURL=http://your-app:3000 \
  --set config.inferenceURL=http://kysira-inference:8081

kysira-proxy Helm values

Value Default Description
config.targetURL http://localhost:3000 Upstream application URL
config.inferenceURL (auto-derived) Inference sidecar URL — defaults to http://<release>-kysira-inference:8081. Override only when inference is deployed separately.
config.mode shadow shadow or active
config.scoreThreshold 0.95 Kill threshold [0–1]
config.sinks file Comma-separated sinks: file, stdout, http
config.httpSinkURL "" Endpoint for the http sink (Datadog, Splunk, etc.)
config.proxyPort 8080 Port to listen on
persistence.enabled true Mount a PVC for events.jsonl and registrations.jsonl
persistence.size 1Gi PVC size
metrics.enabled true Add Prometheus scrape annotations to the pod
replicaCount 1 Number of proxy replicas

kysira-inference Helm values

Value Default Description
env.KYSIRA_DEVICE cpu cpu, cuda, or mps — models run offline, baked into the image
resources.requests.memory 1Gi Minimum memory (models are ~1.5 GB resident)
persistence.enabled false Models are baked into the image; no PVC needed unless caching external models

Model 2 — Istio ext_proc

kysira-ext-proc implements the Envoy External Processing gRPC protocol. The Istio sidecar calls it per request over gRPC — no additional proxy hop, lower latency.

Client → Envoy sidecar ──ext_proc gRPC──▶ kysira-ext-proc :50051
                                         kysira-inference :8081
         Your App

Helm install

helm install kysira-ext-proc oci://ghcr.io/kysira/charts/kysira-ext-proc \
  --namespace kysira \
  --set "envoyFilter.workloadSelector.app=your-app" \
  --set config.inferenceURL=http://kysira-inference:8081

The chart creates an EnvoyFilter resource in the istio-system namespace (configurable via envoyFilter.namespace). The filter inserts the ext_proc HTTP filter into every sidecar matching workloadSelector, just before the router filter.

kysira-ext-proc Helm values

Value Default Description
config.inferenceURL http://kysira-inference:8081 Inference sidecar URL
config.mode shadow shadow or active
config.scoreThreshold 0.95 Kill threshold [0–1]
service.grpcPort 50051 gRPC listen port
service.metricsPort 9090 HTTP port for health + metrics + mode API
envoyFilter.enabled true Create the EnvoyFilter resource
envoyFilter.context SIDECAR_INBOUND Envoy listener context
envoyFilter.workloadSelector {} Label selector for target workloads
envoyFilter.namespace istio-system Namespace where the EnvoyFilter is created
envoyFilter.messageTimeout 500ms Per-message gRPC timeout
envoyFilter.failureModeAllow true Pass requests through if ext_proc is unreachable

Fail-open behaviour

Both deployment models fail open: if the inference service is unreachable, the request is passed through unmodified (proxy logs the error; ext_proc behaviour is controlled by failureModeAllow). This prevents Kysira from becoming a single point of failure.

Port exclusion for ext_proc

The ext_proc deployment has traffic.sidecar.istio.io/excludeInboundPorts: "50051" on its pod so Istio does not intercept the gRPC connections from Envoy sidecars — otherwise Envoy would route the ext_proc call through itself, creating a loop.


Model 3 — Standalone Envoy (without Istio)

kysira-ext-proc works with any Envoy deployment, not just Istio. If the customer runs Envoy directly (as an API gateway, via Contour, or as a standalone edge proxy), they configure the ext_proc filter in their envoy.yaml instead of using an EnvoyFilter CRD.

Deploy only the ext-proc service and inference — skip the EnvoyFilter resource by setting envoyFilter.enabled: false:

helm install kysira-inference oci://ghcr.io/kysira/charts/kysira-inference \
  --namespace kysira \
  --set config.device=cpu

helm install kysira-ext-proc oci://ghcr.io/kysira/charts/kysira-ext-proc \
  --namespace kysira \
  --set config.inferenceURL=http://kysira-inference:8081 \
  --set envoyFilter.enabled=false

Then add the ext_proc filter to the customer's Envoy config. The filter goes in the HTTP filter chain, before the router:

static_resources:
  clusters:
    - name: kysira_ext_proc
      type: STRICT_DNS
      http2_protocol_options: {}      # gRPC requires HTTP/2
      load_assignment:
        cluster_name: kysira_ext_proc
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: kysira-ext-proc.kysira.svc.cluster.local
                      port_value: 50051

  listeners:
    - name: ingress
      address:
        socket_address: { address: 0.0.0.0, port_value: 8080 }
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                http_filters:
                  - name: envoy.filters.http.ext_proc
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
                      grpc_service:
                        envoy_grpc:
                          cluster_name: kysira_ext_proc
                        timeout: 0.5s
                      failure_mode_allow: true   # fail open if ext_proc is unreachable
                      processing_mode:
                        request_header_mode: SEND
                        request_body_mode: BUFFERED
                        response_header_mode: SKIP
                        response_body_mode: SKIP
                      message_timeout: 0.5s
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                # ... rest of HCM config (route_config, stat_prefix, etc.)

The gRPC cluster must have http2_protocol_options: {} — ext_proc uses gRPC which requires HTTP/2. All other behaviour (shadow/active mode, scoring, fail-open) is identical to the Istio integration.

Contour / Envoy Gateway

Contour and Envoy Gateway both support custom Envoy configuration via ExtensionService or EnvoyPatchPolicy resources. The kysira-ext-proc gRPC endpoint is the same — only the mechanism for injecting the filter differs per project. Refer to the Contour ExtensionService docs or Envoy Gateway EnvoyPatchPolicy docs for the specific API.


Model 4 — nginx via auth_request

Standard nginx cannot make inline HTTP calls, but it can delegate auth decisions to an external service using the ngx_http_auth_request_module (included in all nginx builds). Kysira ships a thin adapter — kysira-nginx-auth-adapter — that acts as the auth_request target.

nginx  ──auth_request──▶  /_kysira/check (internal location)
                                   │ proxy_pass
                          kysira-nginx-auth-adapter:8090/check
                                   │ POST /score/all
                          kysira-inference:8081
                          200 (pass) or 403 (block)
nginx  ──proxy_pass──▶  your-app   (if 200)
nginx  ──403──▶  client            (if 403, via error_page)

Deploy

helm install kysira-inference oci://ghcr.io/kysira/charts/kysira-inference \
  --namespace kysira \
  --set config.device=cpu

helm install kysira-nginx-auth-adapter oci://ghcr.io/kysira/charts/kysira-nginx-auth-adapter \
  --namespace kysira \
  --set config.inferenceURL=http://kysira-inference:8081 \
  --set config.mode=shadow

nginx configuration

http {
    # Buffer request bodies so auth_request can forward them to the adapter.
    # Without buffering, POST body scoring is skipped (the adapter still scores
    # URI + method + headers, which catches most GET-based attacks).
    client_body_buffer_size  64k;
    client_max_body_size     10m;

    server {
        listen 80;

        location / {
            # Delegate every request to the Kysira adapter before proxying.
            auth_request     /_kysira/check;

            # Capture scoring headers from the adapter response so they can be
            # forwarded to the upstream app as request headers.
            auth_request_set $kysira_score   $upstream_http_x_kysira_score;
            auth_request_set $kysira_reason  $upstream_http_x_kysira_reason;
            auth_request_set $kysira_mode    $upstream_http_x_kysira_mode;

            proxy_set_header X-Kysira-Score  $kysira_score;
            proxy_set_header X-Kysira-Reason $kysira_reason;
            proxy_set_header X-Kysira-Mode   $kysira_mode;

            proxy_pass http://your-app;

            # Custom error page when the adapter returns 403 (active mode).
            error_page 403 = @kysira_blocked;
        }

        # Internal-only location — nginx does not expose this externally.
        location = /_kysira/check {
            internal;

            proxy_pass              http://kysira-nginx-auth-adapter:8090/check;
            proxy_pass_request_body on;
            proxy_set_header        X-Original-URI    $request_uri;
            proxy_set_header        X-Original-Method $request_method;
            proxy_set_header        Content-Type      $content_type;

            # Never cache auth decisions.
            proxy_no_cache      1;
            proxy_cache_bypass  1;
        }

        location @kysira_blocked {
            default_type application/json;
            return 403 '{"error":"forbidden","detail":"request blocked by Kysira WAF"}';
        }
    }
}

kysira-nginx-auth-adapter Helm values

Value Default Description
config.inferenceURL http://kysira-inference:8081 Inference service URL
config.mode shadow shadow (score + log, never block) or active (block above threshold)
config.scoreThreshold 0.95 Kill threshold [0–1]
service.port 8090 Port the adapter listens on
replicaCount 2 Replicas — keep ≥ 2 so rolling updates don't cause auth failures

Behaviour notes

Fail-open: If the adapter is unreachable, nginx's auth_request returns a 500 error. To prevent this from blocking traffic, add error_page 500 = @kysira_failopen; alongside the 403 handler and have it proxy through. Alternatively set proxy_next_upstream error on the check location.

Body scoring: proxy_pass_request_body on forwards the buffered body to the adapter. nginx buffers bodies up to client_body_buffer_size in memory; larger bodies spill to disk then forward. For APIs that receive large uploads, tune client_body_buffer_size or set the adapter to score URI-only for those paths.

Shadow vs active: In shadow mode the adapter always returns 200 — flagged requests are logged but not blocked. Switch to active with --set config.mode=active or update the ConfigMap directly.


Kustomize overlays

The kustomize/ directory provides a base plus three overlays:

kustomize/
├── base/                    # shared Deployment + Service manifests
├── overlays/
│   ├── dev/                 # single replica, debug logging, emptyDir storage
│   ├── prod/                # HPA, PDB, resource limits, PVC storage
│   └── demo/                # Juice Shop + NodePort services (:30080, :30090)

Apply an overlay

# Development (uses emptyDir — no PVC needed)
kubectl apply -k kustomize/overlays/dev

# Production
kubectl apply -k kustomize/overlays/prod

# Full demo with Juice Shop (minikube)
kubectl apply -k kustomize/overlays/demo

Namespace

All overlays deploy into the kysira namespace. Create it first if it does not exist:

kubectl create namespace kysira

Umbrella chart — kysira-platform

kysira-platform is a Helm umbrella chart that installs all four components (inference, proxy, dashboard, ext-proc) with one command and exposes a unified values.yaml.

helm install kysira oci://ghcr.io/kysira/charts/kysira-platform \
  --namespace kysira --create-namespace \
  -f my-values.yaml

Sub-chart values are namespaced by component:

# my-values.yaml
kysira-proxy:
  config:
    targetURL: http://my-app:3000
    mode: active

kysira-inference:
  resources:
    requests:
      memory: 2Gi

kysira-dashboard:
  replicaCount: 2

Prometheus metrics

All three Go services expose metrics at /metrics (Prometheus text format). The proxy and ext-proc Helm charts add scrape annotations to pods when metrics.enabled: true:

annotations:
  prometheus.io/scrape: "true"
  prometheus.io/path: "/metrics"
  prometheus.io/port: "8080"   # proxy
  # or :9090 for ext-proc

Proxy metric names

Metric Type Labels
kysira_requests_total Counter action (passed/active_kill/shadow_kill/error/skipped)
kysira_request_duration_seconds Histogram action
kysira_flagged_total Counter
kysira_killed_total Counter
kysira_inference_errors_total Counter

ext-proc metric names

Metric Type Labels
kysira_extproc_requests_total Counter action
kysira_extproc_request_duration_seconds Histogram action
kysira_extproc_flagged_total Counter
kysira_extproc_killed_total Counter
kysira_extproc_inference_errors_total Counter
kysira_extproc_active_streams Gauge

Pulling registration data

Interested contacts who click "Register" in the dashboard are saved to registrations.jsonl in the proxy data volume.

# Docker Compose
docker run --rm -v kysira_proxy-data:/data alpine \
  sh -c 'cat /data/registrations.jsonl'

# Kubernetes
kubectl exec -n kysira deployment/kysira-proxy -- cat /data/registrations.jsonl