Documentation
Getting Started
Kvlar is an open-source policy engine and runtime security layer for AI agents. It sits between your agent and its tools, evaluating every action against YAML-based security policies before execution.
Install
# Install from crates.io
cargo install kvlar-cli
# Or build from source
git clone https://github.com/kvlar-io/kvlar && cd kvlar
cargo build --release
# Binary at: target/release/kvlarInitialize a policy
# Create ~/.kvlar/policy.yaml from a starter template
kvlar init # default template (9 rules)
kvlar init --template strict # strict — deny-heavy
kvlar init --template permissive # permissive — allow-heavy
kvlar init --template filesystem # @modelcontextprotocol/server-filesystem
kvlar init --template postgres # @modelcontextprotocol/server-postgres
kvlar init --template github # @modelcontextprotocol/server-github
kvlar init --template slack # @modelcontextprotocol/server-slack
kvlar init --template shell # Shell/exec MCP serversWrap your MCP servers
# Inject Kvlar into your MCP client config
kvlar wrap # auto-detect Claude Desktop or Cursor
kvlar wrap --dry-run # preview changes
kvlar wrap --client cursor # target Cursor
kvlar wrap --config ./mcp.json # use a custom config fileRestart your MCP client after wrapping. Every tool call now flows through Kvlar's policy engine.
Test your policy
# policy.test.yaml
policy: "../policy.yaml"
tests:
- id: deny-bash
action:
resource: bash
expect: deny
rule: deny-shell
- id: allow-read
action:
resource: read_file
expect: allowkvlar test -f policy.test.yaml # human output
kvlar test -f policy.test.yaml --verbose # show passing tests
kvlar test -f policy.test.yaml --json # JSON for CIUnwrap (remove Kvlar)
kvlar unwrap # restore original MCP server commandsPolicy Reference
Policy structure
A Kvlar policy is a YAML file with the following structure:
name: my-policy
description: Security policy for my agent
version: "1.0"
rules:
- id: rule-name
description: Human-readable description
match_on:
action_types: ["tool_call"]
resources: ["resource_name"]
agent_ids: ["agent-*"]
effect:
type: allow | deny | require_approval
reason: "Why this rule exists"Rule matching
Rules are evaluated in order. The first matching rule determines the decision. If no rule matches, the action is denied (fail-closed).
Match fields
resources— Tool or resource names to match (e.g.,"read_file","delete_*")action_types— Action type filter (e.g.,"tool_call")agent_ids— Agent identity filter with glob support
Glob patterns
Match fields support glob/wildcard patterns:
*— matches any characters (e.g.,read_*matchesread_file,read_text_file)?— matches a single character[abc]— matches any character in the set[!abc]— matches any character NOT in the set
Parameter matching
Match on tool arguments using regex patterns or conditional expressions:
# Regex pattern matching on parameters
match_on:
resources: ["query"]
parameters:
sql: "(?i)\\bDROP\\b" # matches SQL containing DROP
# Conditional expressions for advanced matching
match_on:
resources: ["query"]
conditions:
- field: sql
operator: contains
value: "DROP"
# Available operators:
# equals, not_equals, contains, starts_with, ends_with,
# greater_than, less_than, exists, not_exists, one_ofEffects
| Effect | Behavior |
|---|---|
allow | Action is forwarded to the MCP server |
deny | Action is blocked — error returned to agent with rule ID and reason |
require_approval | Action is blocked pending human approval |
Example: Filesystem policy
name: filesystem-demo
version: "1.0"
rules:
- id: deny-delete
match_on:
resources: ["delete_directory"]
effect:
type: deny
reason: "Directory deletion is blocked"
- id: approve-write
match_on:
resources: ["write_file"]
effect:
type: require_approval
reason: "File writes require human approval"
- id: allow-reads
match_on:
resources: ["read_*"]
effect:
type: allowServer-specific policies
Kvlar ships curated policies for popular MCP servers. Each policy is designed around the actual tools exposed by that server.
| Template | Server | Deny | Approve | Allow |
|---|---|---|---|---|
postgres | server-postgres | DROP, TRUNCATE, ALTER, GRANT, REVOKE | DELETE, UPDATE, INSERT, CREATE | SELECT, read queries |
github | server-github | create_repository, fork_repository | push_files, merge_pull_request | get_*, list_*, search_* |
slack | server-slack | — | post_message, reply_to_thread | All read operations |
shell | Shell/exec servers | rm -rf, sudo, curl|bash | mv, cp, pip/npm install | ls, cat, grep, echo |
Policy composition
Use the extends directive to compose policies. Your rules take priority (first-match wins), and base policy rules are appended after yours.
name: my-custom-policy
version: "1.0"
extends:
- postgres # built-in template
- ./extra.yaml # file path
rules:
# Your overrides go here — evaluated first
- id: allow-inserts
match_on:
resources: ["query"]
parameters:
sql: "(?i)\\bINSERT\\b"
effect:
type: allow # override postgres template's require_approvalAvailable built-in names: default, strict, permissive, filesystem, postgres, github, slack, shell.
Approval Webhooks
When a policy rule uses require_approval, the proxy can route the decision to an external system via HTTP webhooks. This enables human-in-the-loop workflows with Slack, email, or custom approval UIs.
How it works
When a tool call matches a require_approval rule, the proxy sends a POST request to your webhook URL with the approval request:
POST https://approvals.internal/kvlar
Content-Type: application/json
{
"tool_name": "query",
"arguments": {"sql": "DELETE FROM users WHERE id = 5"},
"matched_rule": "approve-data-modification",
"request_id": "req_abc123",
"timestamp": "2026-03-05T10:30:00Z"
}Webhook response
Your webhook responds with an approval decision:
// Approved
{"decision": "approved"}
// Denied (with optional reason)
{"decision": "denied", "reason": "Not allowed during peak hours"}Configuring the webhook
The webhook backend is configured programmatically via the Rust API. CLI integration is coming in a future release.
use kvlar_proxy::WebhookApprovalBackend;
use std::time::Duration;
let backend = WebhookApprovalBackend::new(
"https://approvals.internal/kvlar",
Duration::from_secs(300), // 5 min timeout for human review
);Designing approval rules
Reserve require_approval for actions that have real consequences but aren't categorically dangerous — data mutations, sending messages, modifying configuration. Actions that are always dangerous should be deny. Actions that are always safe should be allow.
Health Checks
The proxy includes a built-in HTTP health endpoint for liveness probes (Docker, Kubernetes, load balancers).
Enable the health endpoint
# CLI flag
kvlar proxy --stdio --policy policy.yaml --health 127.0.0.1:9101 -- npx server-name
# Or in proxy config YAML
health_addr: "127.0.0.1:9101"Response format
GET /health HTTP/1.1
{
"status": "ok",
"uptime_secs": 3600,
"policy_loaded": true,
"rules_count": 14,
"requests_evaluated": 1247,
"requests_allowed": 1189,
"requests_denied": 42,
"requests_approval": 16,
"version": "0.3.0"
}Docker health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:9101/health || exit 1Kubernetes liveness probe
livenessProbe:
httpGet:
path: /health
port: 9101
initialDelaySeconds: 5
periodSeconds: 30Python SDK
The Python SDK wraps the kvlar CLI binary, providing a native Python interface for policy evaluation, validation, and testing.
Installation
pip install kvlar
# Requires the kvlar CLI binary on PATH
cargo install kvlar-cliEvaluate an action
from kvlar import KvlarEngine, Decision
engine = KvlarEngine()
result = engine.evaluate(
policy="policy.yaml",
resource="query",
parameters={"sql": "DROP TABLE users"}
)
if result.decision == Decision.DENY:
print(f"Blocked: {result.reason}")
elif result.decision == Decision.ALLOW:
print("Action allowed")
elif result.decision == Decision.REQUIRE_APPROVAL:
print("Needs human approval")Validate a policy
from kvlar import KvlarEngine, KvlarError
engine = KvlarEngine()
try:
engine.validate("policy.yaml")
print("Policy is valid")
except KvlarError as e:
print(f"Invalid: {e}")Run test suites
result = engine.test_policy("tests.yaml")
print(f"{result.passed}/{result.total} tests passed")
print(f"All passed: {result.all_passed}")TypeScript SDK
The TypeScript SDK wraps the kvlar CLI binary, providing a type-safe interface for policy evaluation, validation, and testing.
Installation
npm install kvlar
# Requires the kvlar CLI binary on PATH
cargo install kvlar-cliEvaluate an action
import { KvlarEngine } from "kvlar";
const engine = new KvlarEngine("policy.yaml");
const result = engine.evaluate({
tool: "query",
arguments: { sql: "DROP TABLE users" },
});
if (result.decision === "deny") {
console.log(`Blocked: ${result.reason}`);
} else if (result.decision === "allow") {
console.log("Action allowed");
} else if (result.decision === "require_approval") {
console.log("Needs human approval");
}Validate a policy
const engine = new KvlarEngine("policy.yaml");
const valid = engine.validate();
console.log(valid ? "Policy is valid" : "Policy has errors");Run test suites
const result = engine.testPolicy("policy.test.yaml");
console.log(`${result.total} tests, ${result.failures} failures`);
console.log(`All passed: ${result.passed}`);Options
const engine = new KvlarEngine("policy.yaml", {
binary: "/usr/local/bin/kvlar", // Custom binary path
timeout: 5000, // CLI timeout in ms
});Audit Export
Export audit logs from the Kvlar proxy in SIEM-compatible formats. Supports JSONL, CEF (Common Event Format), and CSV with flexible filtering by time range, outcome, resource, and agent.
Export to CEF (Splunk, ArcSight, QRadar)
# Export all events as CEF
kvlar audit export -f audit.jsonl --format cef
# Export denied events from the last 24 hours
kvlar audit export -f audit.jsonl --format cef \
--outcome denied \
--since 2026-03-04T00:00:00Z
# Export to file
kvlar audit export -f audit.jsonl --format cef -o export.cefExport to CSV
# Export all events as CSV
kvlar audit export -f audit.jsonl --format csv -o audit.csv
# Filter by resource and agent
kvlar audit export -f audit.jsonl --format csv \
--resource bash --agent agent-1Audit statistics
kvlar audit stats -f audit.jsonl
# Output:
# Audit Log Summary
# ─────────────────
# Events: 1,247
# Period: 2026-03-01 09:15 → 2026-03-04 17:42
#
# Outcomes:
# Allowed: 892
# Denied: 301
# Pending Approval: 54
#
# Top Resources:
# read_file 412
# bash 301
# query 198Supported formats
| Format | Flag | Use case |
|---|---|---|
| JSONL | --format jsonl | Splunk, Datadog, Elastic, custom pipelines |
| CEF | --format cef | ArcSight, QRadar, Splunk Enterprise Security |
| CSV | --format csv | Spreadsheets, data analysis, reporting |
Filter options
| Flag | Description |
|---|---|
--since | Events after this time (RFC 3339) |
--until | Events before this time (RFC 3339) |
--outcome | Filter by outcome: allowed, denied, pending_approval |
--resource | Filter by resource (substring match) |
--agent | Filter by agent ID (substring match) |
CLI Reference
| Command | Description |
|---|---|
kvlar init | Create a starter policy file (~/.kvlar/policy.yaml) |
kvlar wrap | Inject Kvlar proxy into MCP client config |
kvlar unwrap | Remove Kvlar wrapping, restore original commands |
kvlar test | Run policy test suites |
kvlar validate | Validate a policy YAML file |
kvlar evaluate | Evaluate a single action against a policy |
kvlar inspect | Show policy summary (rules, effects) |
kvlar schema | Export JSON Schema for policy validation |
kvlar proxy | Start the MCP security proxy (stdio or TCP) |
kvlar audit export | Export audit logs (JSONL, CEF, CSV) with filtering |
kvlar audit stats | Show audit log summary statistics |
kvlar init
kvlar init [--template <name>] [--dir <path>]
Templates: default, strict, permissive, filesystem,
postgres, github, slack, shell
Output: ~/.kvlar/policy.yaml (or --dir)kvlar wrap
kvlar wrap [--client <name>] [--config <path>] [--dry-run]
Clients: claude-desktop (default), cursor
Options:
--dry-run Preview changes without writing
--client Target a specific MCP client
--config Path to MCP client config file
--policy Policy file (default: ~/.kvlar/policy.yaml)
--only Wrap only specific servers
--skip Skip specific serverskvlar test
kvlar test -f <test-file> [--policy <file>] [--json] [--verbose]
Options:
-f, --file Test suite YAML file (required)
--policy Override policy file path
--json JSON output for CI
--verbose Show passing testskvlar proxy
kvlar proxy [--policy <file>] [--stdio] [--watch] [--port <n>] [--health <addr>] -- <upstream-command> [args...]
Options:
--policy Policy file (default: ~/.kvlar/policy.yaml)
--stdio Stdio transport mode (for MCP clients)
--watch Watch policy files and reload on change
--port TCP port (default: 3000)
--health Health check endpoint (e.g., 127.0.0.1:9101)
-- Upstream MCP server command (stdio mode)Policy hot-reload
Use --watch to automatically reload policies when the YAML file changes. No proxy restart needed.
# Start proxy with hot-reload
kvlar proxy --stdio --policy policy.yaml --watch -- npx server-name
# Edit policy.yaml in your editor — changes apply instantly
# If the new policy has errors, the old policy stays activeHot-reload uses filesystem watching with debouncing. All reload messages go to stderr, so they're safe in stdio mode. You can also enable it via hot_reload: true in a proxy config file.
Architecture
Agent ──stdio/TCP──► kvlar-proxy ──stdio/TCP──► MCP Tool Server
│
kvlar-core (policy evaluation)
│
kvlar-audit (structured logging)Crates
| Crate | Purpose |
|---|---|
kvlar-core | Policy engine — pure logic, no I/O, fully deterministic |
kvlar-proxy | MCP security proxy — intercepts and evaluates tool calls |
kvlar-audit | Structured audit logging — JSONL and human output |
kvlar-cli | CLI tool — 9 commands for policy management and proxy |
Design principles
- Fail-closed — deny if no rule matches (ADR-001)
- YAML policies — human-readable, version-controllable (ADR-002)
- Pure core — kvlar-core has zero I/O, fully deterministic (ADR-003)
- Stdio MITM — proxy spawns upstream server as child process (ADR-004)
MCP Compatibility
| Property | Value |
|---|---|
| Spec version | 2024-11-05 |
| Transport | stdio (primary), TCP |
| Protocol | JSON-RPC 2.0 over newline-delimited JSON |
| Tested with | Claude Desktop, Cursor, server-filesystem, server-postgres, server-github, server-slack |