norviq

mcp
Security Audit
Pass
Health Pass
  • License — License: Apache-2.0
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 20 GitHub stars
Code Pass
  • Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

Runtime security platform for LLM agent tool calls on Kubernetes

README.md
Norviq

Norviq

Runtime policy enforcement for LLM agent tool calls on Kubernetes.

FOSSA Security
License
Kubernetes
OPA
Docs

Documentation · Website · Getting started

Norviq is a policy enforcement point (PEP) that sits between an AI agent's reasoning loop and the
tools it can call. Every tool call is intercepted, evaluated against OPA/Rego policies scoped to the
workload's Kubernetes/SPIFFE identity, and then allowed, blocked, escalated, or audited — before
the tool runs. It turns "the model decided to call execute_sql / send_email / shell" from an
implicit trust into an enforced, per-identity, auditable decision.


Why

LLM agents are given real tools — databases, shells, email, cloud APIs, internal services. The model
chooses which to call at runtime, and a single prompt injection or reasoning error can turn a benign
agent into an exfiltration or destruction path. Norviq puts a deterministic, policy-driven gate on
that surface, so a tool call only happens if an explicit policy for that agent's identity allows it.

How it works

Every tool call takes a round trip through the engine before it executes:

sequenceDiagram
    autonumber
    participant Agent as Agent (LangGraph / SDK)
    participant PEP as Norviq sidecar / SDK
    participant Engine as Norviq API / engine
    participant OPA as OPA / Rego
    Agent->>PEP: tool call — {tool, params, identity}
    PEP->>Engine: POST /evaluate
    Note over Engine: resolve SPIFFE identity<br/>collect policy tiers + overlays
    Engine->>OPA: evaluate
    OPA-->>Engine: decision
    Engine-->>PEP: allow / block / escalate / audit<br/>+ rule_id + reason
    PEP-->>Agent: enforced decision
    Engine->>Engine: audit log · trust score · asset/attack graph
  • Interception — an injected sidecar (or the SDK) forwards each tool call to the engine's /evaluate.
  • Identity — decisions are scoped to the calling workload's SPIFFE identity (SPIRE SVID), not a
    shared secret, so policy is per-agent-class and per-namespace.
  • Policy — Rego policies are layered in tiers (agent-class → namespace baseline → cluster baseline)
    with tighten-only overlays; the most-restrictive matching rule wins.
  • Modesblock (deny + reason), escalate, audit (log only / monitor mode), so you can roll
    out enforcement observably before turning it on.

Deployed components

flowchart LR
    subgraph tenant["Agent namespace"]
        pod["Agent pod<br/>+ Norviq sidecar (PEP)"]
    end
    subgraph norviq["norviq namespace"]
        api["API<br/>+ OPA sidecar"]
        engine["Engine<br/>+ OPA sidecar"]
        pg[("PostgreSQL")]
        redis[("Redis")]
        ui["Console UI"]
        webhook["Admission<br/>webhook"]
    end
    pod -->|POST /api/v1/evaluate| api
    api --> pg
    api --> redis
    engine --> pg
    engine --> redis
    webhook -. injects sidecar .-> pod
    webhook -. syncs NrvqPolicy/NrvqClass .-> api
    ui --> api

Both the API and the Engine evaluate in-process against their own OPA sidecar (one OPA per replica,
bound to 127.0.0.1) — neither proxies to the other. In the default injection mode
(webhook.injection.sidecarMode: proxy) the injected sidecar POSTs every tool call to the central API,
so Postgres, Redis, and policy loading stay centralized and nothing is evaluated per-pod. The Engine
Deployment runs that same evaluator as a standalone cluster workload (NRVQ_SIDECAR_MODE=embedded,
exposed as norviq-engine:8282) for callers that want to evaluate without going through the API.

Works with your agent framework

The sidecar above is zero-code-change. For in-process interception instead — no sidecar, your
own event loop — the SDK (norviq/sdk/) wraps the tool-calling point of these frameworks so a
block/escalate decision raises before the tool ever runs. See
docs/guides/integrating-agents.md for setup and snippets.

  • LangChainnorviq.sdk.langchain.adapter.protect(tools, interceptor)
  • LangGraphnorviq.sdk.langgraph.adapter.GuardedToolNode(tools, interceptor)
  • CrewAInorviq.sdk.crewai.adapter.protect(tools, interceptor)
  • AutoGennorviq.sdk.autogen.adapter.protect(tools, interceptor)
  • Azure / Semantic Kernelnorviq.sdk.semantic_kernel.adapter.policy_filter(interceptor)

examples/chatbot/ is a runnable LangChain/LangGraph chatbot (Groq) where a
real model decides the tool calls and Norviq blocks the dangerous ones before they run — with a
Dockerfile and k8s/ manifests for running it in-cluster behind the injected sidecar.

Features

  • Policy enforcement — OPA/Rego evaluated per tool call, sub-second, fail-closed.
  • Kubernetes-nativeNrvqPolicy / NrvqClass / NrvqConfig CRDs, a mutating webhook that
    injects the enforcement sidecar, and a Helm chart.
  • Workload identity — SPIFFE/SPIRE SVIDs (with a mock mode for non-SPIRE clusters).
  • Console UI — policy catalog + editor, attack graph, asset graph, agent trust, audit stream.
  • Red-team suite — built-in adversarial tests (prompt injection, encoding/nesting evasion, SQLi,
    PII/PCI exfil) that prove a policy actually blocks.
  • Compliance mapping — MITRE ATLAS and OWASP LLM Top-10 coverage with generate-enforcing-policy
    remediation.
  • High availability — multi-replica with cross-replica policy propagation and DB-authoritative
    deletes; HPA/PDB/anti-affinity for multi-node clusters.
  • Multi-cluster (fleet) — signed policy-bundle distribution across a hub and spoke clusters.

Quick start

Prerequisites: a Kubernetes cluster (1.30+), kubectl, and Helm 3.

kubectl create namespace norviq

# Create the tenant namespaces FIRST. Each one listed in policyQuotaNamespaces gets a ResourceQuota
# and a baseline policy, and both are namespaced — so the install fails on a namespace that does not
# exist yet. (The chart now says so by name if you forget, rather than failing partway through.)
kubectl create namespace default 2>/dev/null || true

# Installs from the published, cosign-signed chart. CRDs ship inside it, and every Norviq image is
# pinned by immutable digest — so this deploys exactly the bytes that release published.
helm install norviq oci://ghcr.io/norviq-dev/charts/norviq --version 0.2.5 -n norviq \
  --set 'policyQuotaNamespaces={default}' \
  --set config.dbSslMode=disable   # the bundled Postgres has no TLS; omit if you point at an external TLS DB

Working on Norviq itself, or a modified chart? Install from a clone instead — apply
helm/norviq/crds/ first, then helm install norviq ./helm/norviq. See
getting-started for that path.

policyQuotaNamespaces is the list of tenant namespaces that will run agents — it is required, not
optional. The chart installs a strict-preset namespace baseline for each entry, so an empty list
fails the install by design rather than shipping a cluster with no baseline. That baseline ships in
audit mode (baselineClusterPolicy.enforcementMode): every control evaluates and records a
non-compliance event, and the call proceeds. It is deliberately not fail-closed — installing 22 block
rules in front of every tool call on day one dropped real traffic — so set enforcementMode: block
when you are ready to enforce. Add every agent namespace
you plan to use, and create them before installing — the baseline policy and quota are namespaced
objects, so the install cannot place them in a namespace that does not exist yet. If one is missing the
chart stops before applying anything and names it.

The chart deploys the API, engine, console UI, mutating webhook, and bundled PostgreSQL + Redis + OPA.
Port-forward the console:

kubectl -n norviq port-forward svc/norviq-ui 8080:80
# open http://localhost:8080

Sign in as admin. The chart generates a random first password on install (it only uses a literal
password if you set auth.adminPassword yourself) — read it back, then change it when the console
prompts you:

kubectl get secret norviq-secrets -n norviq -o jsonpath='{.data.NRVQ_AUTH_ADMIN_PASSWORD}' | base64 -d

This is the FIRST password only. The Secret is written at install and never updated
again, so once you complete the forced change it is stale — the live password lives hashed
in Postgres. If it is rejected, you already changed it; the Secret is not a recovery path.

Sidecar injection ships off (webhook.injection.enabled: false). Turn it on, then label the same
namespaces you listed in policyQuotaNamespaces — the label alone does nothing until the webhook is
enabled:

helm upgrade norviq oci://ghcr.io/norviq-dev/charts/norviq --version 0.2.5 -n norviq --reset-then-reuse-values --set webhook.injection.enabled=true
kubectl label namespace <your-agent-namespace> norviq-injection=enabled

--reset-then-reuse-values (Helm ≥ 3.14), not --reuse-values: the latter replays only the values
you supplied last time and does not merge in values a newer chart added, so an upgrade across a
release that introduced one fails with nil pointer evaluating interface {}. On older Helm, pass
your own -f values.yaml.

Then label each agent pod norviq.io/agent-class=<class> — that pod label is what gets the
enforcement sidecar injected, not the namespace label alone. The namespace label opts the namespace
in; the pod label says which pods are agents. A pod without it starts un-injected and ungoverned.

That is deliberate (webhook.injection.gateOnlyAgentPods, default true): with failurePolicy: Fail,
routing every pod in the namespace through the webhook would make Norviq's availability a precondition
for starting that namespace's database and ingress too. Set it to false for namespace-wide injection.

Trying it locally? A single-node kind cluster is enough to evaluate
everything except multi-node HA. See docs/getting-started.md.

Documentation

Full documentation is at docs.norviq.dev:

  • Getting started — install, first login, sidecar injection, first policy
  • Concepts — agent classes, policy tiers, enforcement modes, trust score, SPIFFE identity
  • Writing policies — the Rego contract, packages, tighten-only overlays, validation
  • Policy cookbook — copy-paste NrvqPolicy recipes + validated Rego building blocks
  • Asset & attack graphs — real reach, kill chains, Simulate, Defend, tool classification
  • Compliance & coverage — MITRE ATLAS / OWASP LLM coverage, gaps, remediation, evidence pack
  • MCP firewall — governing MCP servers: the discovery and invocation gates, definition pinning and drift, and writing policy against input.mcp
  • Integrating agents — the SDK: LangChain, LangGraph, CrewAI, AutoGen, Semantic Kernel
  • CLI referencenorviq login, policies, audit, agents, red-team, fleet
  • Configuration — Helm values.yaml reference
  • Deployment — production HA, cloud (AKS / EKS / GKE), and multi-cluster fleet
  • Security model — trust boundaries and the threat model

Runnable examples live under examples/; engineering references under
docs/engineering/.

Development

pip install -e ".[dev]"   # backend + test tooling
make test                 # pytest tests/
make lint                 # ruff check norviq/ tests/

The console suite runs from ui/ with npm test (vitest). The shipped Rego is v0-syntax, so its suite
needs the compatibility flag:

opa test --v0-compatible webhook/presets/ comprehensive.rego

The stack is Python (FastAPI) + OPA/Rego for the engine, React + Vite (TypeScript) for the console, and
Go for the admission webhook. See CONTRIBUTING.md.

Security

Found a vulnerability? Please follow the coordinated-disclosure process in SECURITY.md
do not open a public issue for security reports.

License

Apache 2.0.

Reviews (0)

No results found