Skip to content

Guardrail ​

Scope: namespaced · Modes: catalog (bundled engine) · external (BYO HTTP) · Licensed: guardrail (Enterprise)

A Guardrail defines a data-path guardrail — content moderation / PII handling that runs inside the request path of a Gateway. Reference it from a Gateway via spec.guardrailRefs; the operator emits the upstream LiteLLMGuardrail wiring for you.

Both modes require the licensed guardrail feature, and both are routed through a small license-gate proxy the operator injects in front of the guardrail. The proxy verifies the signed license and fails closed: if the license lapses (expired, revoked, or removed) every request receives a guardrail block verdict instead of being forwarded — the guardrail stops adding value the moment you are no longer licensed. Unlicensed clusters get a Refused Guardrail and nothing is wired.

Spec ​

FieldTypeDescription
modeenum catalog | external (required)Bundled engine vs. your own HTTP endpoint
catalogstringBundled engine key (mode=catalog), e.g. pseudonymizer
externalobjectYour HTTP guardrail (mode=external)
guardrailNamestringOverride the LiteLLM guardrail_name (defaults to the catalog key or CR name)
modes[]enum pre_call | post_call | during_call | logging_onlyExecution modes; one LiteLLMGuardrail is emitted per mode. Defaults to the engine's modes (the pseudonymizer runs pre_call+post_call) or [pre_call] for external
defaultOnboolRun on every request even without an explicit key/team opt-in. Catalog engines default on; external defaults off
paramsobject (arbitrary JSON)Provider params forwarded to LiteLLM under additional_provider_specific_params (merged over the engine defaults)
logLevelenum debug | info | warn | errorVerbosity of the license-gate proxy. debug logs one record per guardrail request — see Troubleshooting. Changing it restarts the proxy pod
blockedReasonstringMessage the proxy returns when it blocks a request because the feature is not licensed. Make it distinctive to recognize a license-gate block in gateway logs

external ​

FieldDescription
apiBaseYour guardrail HTTP endpoint (LiteLLM generic_guardrail_api; it POSTs to {apiBase}/beta/litellm_basic_guardrail_api)
apiKeySecretRefSame-namespace Secret whose value the proxy forwards to your endpoint as x-api-key
unreachableFallbackfail_closed (default) or fail_open — LiteLLM's behavior if the guardrail is unreachable

Modes ​

  • catalog — the operator deploys the bundled engine as one shared, ref-counted release for the cluster (in navique-guardrail-system) and puts a per-Guardrail license-gate proxy in front. The first engine is pseudonymizer (PII pseudonymization: masks people/organization names before the model sees them and restores them in the response). Since 0.4.0 it serves the Wäg guardrail protocol on a second endpoint as well, against the same mapping store.
  • external — the operator deploys only the license-gate proxy, pointed at your apiBase. Still licensed and proxied (unlike external MCP servers, BYO guardrails are not free).

How it wires into a Gateway ​

Add the Guardrail to a Gateway's guardrailRefs. Once the Guardrail is Ready, the Gateway emits a LiteLLMGuardrail per mode whose apiBase is the Guardrail's license-gate proxy (status.endpoint) — never the raw engine. Removing the reference (or deleting the Guardrail) prunes the emitted wiring.

Example — bundled PII pseudonymizer ​

yaml
apiVersion: core.navique.com/v1alpha1
kind: Guardrail
metadata:
  name: pii-pseudonymizer
  namespace: forge
spec:
  mode: catalog
  catalog: pseudonymizer
---
apiVersion: core.navique.com/v1alpha1
kind: Gateway
metadata:
  name: forge-gw
  namespace: forge
spec:
  # … database/instance/secrets …
  guardrailRefs:
    - { name: pii-pseudonymizer }

Example — bring your own HTTP guardrail ​

yaml
apiVersion: core.navique.com/v1alpha1
kind: Guardrail
metadata:
  name: my-moderation
  namespace: forge
spec:
  mode: external
  modes: [pre_call]
  external:
    apiBase: https://guardrail.example.com
    unreachableFallback: fail_closed
    apiKeySecretRef: { name: my-guardrail-key, key: apiKey }

Troubleshooting a failed guardrail call ​

A guardrail block is indistinguishable to the caller by design, which leaves three failure modes that look identical from the gateway:

  1. the gateway never called the guardrail and failed the request for another reason,
  2. the license-gate proxy blocked it (unlicensed / expired / revoked),
  3. the engine blocked it, or could not be reached.

The proxy is the only component that sees all three, so it reports them two ways.

Counters (always on) ​

Every proxy serves Prometheus counters on port 9090, separate from the data port (8080) so they cannot shadow a path on your guardrail engine:

sh
kubectl port-forward -n forge svc/pii-pseudonymizer-guardrail-proxy 9090:9090
curl -s localhost:9090/metrics
MetricAnswers
navique_guardrail_proxy_requests_total{decision="forwarded"}did the gateway call the guardrail at all? A flat counter means it never did
…{decision="blocked_unlicensed"}did the license gate block it?
…{decision="upstream_error"}was the engine unreachable?
navique_guardrail_proxy_upstream_verdicts_total{action="…"}what did the engine decide — NONE, GUARDRAIL_INTERVENED or BLOCKED?
navique_guardrail_proxy_upstream_duration_seconds_{sum,count}how slow are the engine calls?
navique_guardrail_proxy_license_validis the mounted license currently satisfying guardrail?

Per-request logging ​

Set spec.logLevel: debug on the Guardrail (this restarts the proxy pod) to get one structured record per request:

json
{"level":"DEBUG","msg":"guardrail request","request_id":"9f2c…","method":"POST",
 "path":"/beta/litellm_basic_guardrail_api","decision":"forwarded","status":200,
 "duration_ms":37,"upstream_action":"BLOCKED","upstream_blocked_reason":"…"}

decision is what the proxy did; upstream_action is the engine's own verdict, read back from the response without altering it. The X-Request-Id header is reused from the caller when present, otherwise minted, forwarded to the engine and echoed on the response — so one id stitches the gateway, proxy and engine logs together.

WARNING

upstream_blocked_reason is the engine's own message and may quote the content it matched on. Raise logLevel while investigating, then lower it again.

Two things the proxy deliberately does not hide ​

  • An unreachable engine returns HTTP 502, not a block. "The engine is down" is not "the engine blocked you": LiteLLM applies the unreachableFallback (fail_closed / fail_open) to a transport failure, and synthesizing a block would silently override a fail_open choice. These failures are logged at error level regardless of logLevel.
  • License-gate blocks are logged when they flip. The proxy logs a warning the moment the license stops satisfying guardrail (and an info line when it starts again), so a lapse is visible without debug logging. Note that /healthz and /readyz stay 200 while failing closed — the pod is healthy, the enforcement is per-request, so don't look to pod readiness for the license state; use navique_guardrail_proxy_license_valid.

Notes ​

  • A determined operator could hand-author a raw upstream LiteLLMGuardrail to skip the license gate; the platform avoids admission webhooks, so the in-core gate is best-effort — only the supported, managed path is gated.
  • The license-gate proxy is a separate, closed-source image (ghcr.io/scigility/navique-guardrail-proxy); override with --guardrail-proxy-image or the PlatformConfig registry host-mirror.

Status ​

{ phase, endpoint (the proxy URL the Gateway wires), guardrailNames, unreachableFallback, conditions, observedGeneration }. Short name gr.

Open core under AGPL-3.0. Enterprise components are proprietary and license-gated.