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/kvlar

Initialize 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 servers

Wrap 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 file

Restart 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: allow
kvlar 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 CI

Unwrap (remove Kvlar)

kvlar unwrap  # restore original MCP server commands

Policy 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_* matches read_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_of

Effects

EffectBehavior
allowAction is forwarded to the MCP server
denyAction is blocked — error returned to agent with rule ID and reason
require_approvalAction 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: allow

Server-specific policies

Kvlar ships curated policies for popular MCP servers. Each policy is designed around the actual tools exposed by that server.

TemplateServerDenyApproveAllow
postgresserver-postgresDROP, TRUNCATE, ALTER, GRANT, REVOKEDELETE, UPDATE, INSERT, CREATESELECT, read queries
githubserver-githubcreate_repository, fork_repositorypush_files, merge_pull_requestget_*, list_*, search_*
slackserver-slackpost_message, reply_to_threadAll read operations
shellShell/exec serversrm -rf, sudo, curl|bashmv, cp, pip/npm installls, 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_approval

Available 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 1

Kubernetes liveness probe

livenessProbe:
  httpGet:
    path: /health
    port: 9101
  initialDelaySeconds: 5
  periodSeconds: 30

Python 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-cli

Evaluate 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-cli

Evaluate 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.cef

Export 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-1

Audit 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                198

Supported formats

FormatFlagUse case
JSONL--format jsonlSplunk, Datadog, Elastic, custom pipelines
CEF--format cefArcSight, QRadar, Splunk Enterprise Security
CSV--format csvSpreadsheets, data analysis, reporting

Filter options

FlagDescription
--sinceEvents after this time (RFC 3339)
--untilEvents before this time (RFC 3339)
--outcomeFilter by outcome: allowed, denied, pending_approval
--resourceFilter by resource (substring match)
--agentFilter by agent ID (substring match)

CLI Reference

CommandDescription
kvlar initCreate a starter policy file (~/.kvlar/policy.yaml)
kvlar wrapInject Kvlar proxy into MCP client config
kvlar unwrapRemove Kvlar wrapping, restore original commands
kvlar testRun policy test suites
kvlar validateValidate a policy YAML file
kvlar evaluateEvaluate a single action against a policy
kvlar inspectShow policy summary (rules, effects)
kvlar schemaExport JSON Schema for policy validation
kvlar proxyStart the MCP security proxy (stdio or TCP)
kvlar audit exportExport audit logs (JSONL, CEF, CSV) with filtering
kvlar audit statsShow 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 servers

kvlar 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 tests

kvlar 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 active

Hot-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

CratePurpose
kvlar-corePolicy engine — pure logic, no I/O, fully deterministic
kvlar-proxyMCP security proxy — intercepts and evaluates tool calls
kvlar-auditStructured audit logging — JSONL and human output
kvlar-cliCLI 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

PropertyValue
Spec version2024-11-05
Transportstdio (primary), TCP
ProtocolJSON-RPC 2.0 over newline-delimited JSON
Tested withClaude Desktop, Cursor, server-filesystem, server-postgres, server-github, server-slack