Lilith Zero
⚸ Lilith Zero - Security Middleware for MCP tool calls written in Rust.
Installation
npx lilith-zeroAsk AI about Lilith Zero
Powered by Claude · Grounded in docs
I know everything about Lilith Zero. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
Lilith Zero is a high-performance security runtime that mitigates data exfiltration and unauthorized tool invocation in LLM-based agent systems. It interposes at the transport layer, enforcing security invariants through deterministic policy evaluation and strictly framed execution.
OS, framework, and language agnostic: uniform security primitives across all implementation environments.
Technical Fundamentals
- Fail-Closed Architecture: defaults to
DENY. Missing policy, parse error, or internal fault → all traffic blocked. - Zero-Trust Transport: stdio and network payloads treated as potentially malicious; strict
Content-Lengthframing prevents JSON smuggling. - Type-Safe Invariants: Rust type system makes invalid security states (e.g. unverified taint propagation) unrepresentable at compile time.
- Deterministic Classification: tool classes are declared explicitly in policy; no runtime heuristics.
Core Capabilities
| Capability | Implementation |
|---|---|
| Deterministic ACLs | Static allow/deny mapping for tool names and resource URIs. |
| Dynamic Taint Tracking | Session-bound sensitivity tags with explicit tool-class declarations. |
| Lethal Trifecta Protection | Auto-blocks the "Access Private → Access Untrusted → Exfiltrate" pattern. |
| Tamper-Proof Audit Logs | HMAC-SHA256 signed JSONL for non-repudiation. |
| Logic-Based Policies | Argument-level enforcement via recursive JsonLogic predicates. |
| Zero-Copy Runtime | <1 ms overhead via reference-based internal message passing. |
| Process Supervision | OS-level lifecycle management (PR_SET_PDEATHSIG / Job Objects / kqueue). |
| Transport Hardening | Content-Length framing; 10 MB body cap; 4 KB header cap. |
| Distributed Tracing | lilith-telemetry integration for cross-process span propagation and audit dashboards. |
System Architecture
graph LR
Agent["AI Agent / LLM"] -->|stdio| Codec
subgraph lz["Lilith Zero (Rust)"]
Codec["Hardened Codec"] --> Coord["Session Coordinator"]
Coord --> Policy["Policy Engine"]
Coord --> Taint["Taint Tracker"]
Coord --> Audit["Audit Logger"]
Coord --> Proc["Process Supervisor"]
end
Proc -->|stdio| ToolLocal["MCP Server (local)"]
Coord -.->|Streamable HTTP| ToolRemote["MCP Server (remote)"]
The agent always communicates with Lilith Zero over stdio: the transport setting only changes how Lilith Zero connects upstream: spawning a local child process (stdio mode) or acting as an HTTP client to a remote MCP server (Streamable HTTP mode). Policy enforcement, taint tracking, and audit logging apply in both cases.
Components
| Component | Role |
|---|---|
| Hardened Codec | LSP Content-Length framing; drops oversized frames before parse |
| Session Coordinator | Tokio actor; owns session state, taint set, history, HMAC token |
| Policy Engine | Static ACL + ordered taint rules; JsonLogic argument predicates |
| Taint Tracker | Tainted<T> / Clean<T> phantom wrappers; only Policy Engine produces Clean<T> |
| Process Supervisor | Platform-native child binding prevents orphan MCP server processes |
| Audit Logger | HMAC-signed JSONL to stderr + optional file; every decision logged |
Agent Integrations
All adapters share the same policy engine, taint tracker, and audit layer: only the I/O contract differs.
Claude Code
Hooks fire on every tool call. Lilith reads the JSON event from stdin and signals via exit code: 0 = allow, 2 = block.
.claude/settings.json (project) or ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [{"matcher": "", "hooks": [{"type": "command", "command": "lilith-zero hook --policy /path/to/policy.yaml"}]}],
"PostToolUse": [{"matcher": "", "hooks": [{"type": "command", "command": "lilith-zero hook --policy /path/to/policy.yaml"}]}]
}
}
See scripts/hooks.json for a ready-to-use template.
GitHub Copilot CLI
Copilot reads {"permissionDecision":"allow"|"deny"} from stdout; exit code is always 0.
~/.config/github-copilot/hooks.json:
{
"preToolUse": {"command": "lilith-zero hook --format copilot --event preToolUse --policy /path/to/policy.yaml"},
"postToolUse": {"command": "lilith-zero hook --format copilot --event postToolUse --policy /path/to/policy.yaml"}
}
See examples/gh-copilot/ for complete policy examples (bash, view, rg, glob, report_intent).
VS Code Copilot Sidebar
Event type is inferred from the payload: no --event flag required.
.vscode/settings.json:
{
"github.copilot.chat.agent.hooks": {
"PreToolUse": {"command": "lilith-zero hook --format vscode --policy /path/to/policy.yaml"},
"PostToolUse": {"command": "lilith-zero hook --format vscode --policy /path/to/policy.yaml"}
}
}
See examples/vscode/ (editFiles, runTerminalCommand, #fetch, #codebase, MCP tools).
OpenClaw
OpenClaw uses stdio MCP only. Wrap each MCP server via lilith-zero run in ~/.openclaw/openclaw.json:
{
"mcp": {
"servers": {
"filesystem": {
"command": "lilith-zero",
"args": ["run",
"--upstream-cmd", "npx -y @modelcontextprotocol/server-filesystem /home/user/projects",
"--policy", "~/.lilith/openclaw-policy.yaml"],
"transport": "stdio",
"env": {"LILITH_ZERO_PIN_FILE": "~/.lilith/pins/filesystem.json"}
}
}
}
}
See examples/openclaw/ for policy-base.yaml (conservative) and policy-paranoid.yaml (read-only).
The examples/openclaw/CVE-COVERAGE.md maps all 139 OpenClaw advisories to the corresponding lilith-zero controls.
Forward-looking: once OpenClaw ships its hook system (#60943), replace the
runwrapper with:
lilith-zero hook --format openclaw --policy ~/.lilith/openclaw-policy.yaml
Copilot Studio Webhook
REST server implementing the Microsoft Copilot Studio External Threat Detection API.
# Dev: no auth
lilith-zero serve --bind 127.0.0.1:8080 --auth-mode none --policy policy.yaml
# Production: Microsoft Entra ID (RS256)
lilith-zero serve --bind 0.0.0.0:8443 \
--auth-mode entra \
--entra-tenant-id <TENANT_GUID> \
--entra-audience https://security.contoso.com \
--policy policy.yaml
Endpoints: POST /validate (health), POST /analyze-tool-execution (policy evaluation).
Session Persistence
Hook mode maintains taint state across binary restarts within a session. State is stored in ~/.lilith/sessions/<session-id>.json under a cross-process file lock (LockFileEx on Windows, flock on Unix). Taints accumulated in one tool call are visible to the next invocation.
Transports
stdio (default)
lilith-zero run --upstream-cmd "python mcp_server.py" --policy policy.yaml
Wraps a child MCP server process. Lilith Zero sits between agent and server on stdin/stdout.
Streamable HTTP (MCP 2025-11-25)
lilith-zero run --transport http --upstream-url http://localhost:9000/mcp --policy policy.yaml
Connects to a remote MCP server; no child process spawned.
SDKs
Python
uv add lilith-zero # auto-downloads the correct binary for your platform
# or: pip install lilith-zero
from lilith_zero import Lilith
async with Lilith("python mcp_server.py", policy="policy.yaml") as lz:
result = await lz.call_tool("read_file", {"path": "/data/report.txt"})
TypeScript / Node.js
bun add @badcompany/lilith-zero
# or: npm install @badcompany/lilith-zero
import { Lilith } from "@badcompany/lilith-zero";
await using lz = new Lilith({ upstream: "bun mcp_server.ts", policy: "policy.yaml" });
const result = await lz.callTool("read_file", { path: "/data/report.txt" });
Setup
1. Install
Python SDK (manages the binary automatically):
uv add lilith-zero
# or: pip install lilith-zero
Shell (Unix/macOS):
curl -sSfL https://badcompany.xyz/lilith-zero/install.sh | sh
PowerShell (Windows):
irm https://badcompany.xyz/lilith-zero/install.ps1 | iex
2. Write a policy
id: my-policy
customer_id: my-org
name: "My Agent Policy"
version: 1
protect_lethal_trifecta: true # auto-blocks ACCESS_PRIVATE + UNTRUSTED_SOURCE → EXFILTRATION
# Declare tool classes explicitly: Lilith performs no heuristic inference.
tool_classes:
curl: [EXFILTRATION]
send_email: [EXFILTRATION]
static_rules:
read_file: ALLOW
calculator: ALLOW
bash: DENY
taint_rules:
- tool: read_file
action: ADD_TAINT
tag: ACCESS_PRIVATE
- tool: curl
action: ADD_TAINT
tag: UNTRUSTED_SOURCE
resource_rules: []
protect_lethal_trifecta: true injects a rule that blocks any tool declared as EXFILTRATION class when both ACCESS_PRIVATE and UNTRUSTED_SOURCE taints are simultaneously active. Tool class assignments are always explicit in the policy: no name-based inference.
3. Wire it to your agent
# Claude Code: one-liner setup
echo '{"hooks":{"PreToolUse":[{"matcher":"","hooks":[{"type":"command","command":"lilith-zero hook --policy $(pwd)/policy.yaml"}]}]}}' \
> .claude/settings.json
4. Environment variables
| Variable | Description | Default |
|---|---|---|
LILITH_ZERO_FORCE_LETHAL_TRIFECTA | Global override: enforces trifecta protection regardless of policy. | false |
LILITH_ZERO_SECURITY_LEVEL | audit_only or block_params. | block_params |
LILITH_ZERO_JWT_SECRET | HMAC secret for external audience token verification. | : |
LOG_LEVEL | debug / info / warn / error. | info |
Observability
Audit Logs
Every decision: ALLOW and DENY: emits a tamper-evident JSONL line to stderr:
[AUDIT] <HMAC-SHA256-base64url> {"session_id":"…","timestamp":…,"event_type":"Decision","details":{"decision":"DENY","tool_name":"curl"}}
Verifying the signature against the session key proves the log was not altered after the fact.
lilith-telemetry
lilith-telemetry is a companion sidecar that aggregates Lilith Zero security events into structured, distributed traces. It implements the TelemetryHook trait and routes spans through a configurable Flock dispatch pipeline.
# Run with telemetry enabled (Flock link from your dashboard)
lilith-zero-telemetry run \
--upstream-cmd "python mcp_server.py" \
--policy policy.yaml \
--flock-link "lilith://collector:7700?key_id=0x<hex>"
Each tool call produces a parent span with child spans for codec framing, policy evaluation, and the upstream call. Decisions are tagged LEVEL: ROUTINE (allow) or LEVEL: CRITICAL (deny/violation):
SESSION: 189e9a3a… TRACE: 189e9a44… SPAN: 189e9a44… LEVEL: CRITICAL KIND: Server
MSG: Blocked tool web_search: Web search prohibited after database access (exfiltration prevention)
Multi-tool interactions from a single LLM reasoning step are grouped into unified trace spans, enabling precise audit of the full agent decision chain.
Security & Assurance
| Layer | Implementation | Status |
|---|---|---|
| Formal Verification | Kani Rust Verifier proofs: taint sanitization, overflow safety, session entropy | Verified |
| Fuzz Testing | Cargo Fuzz: JSON-RPC codec and policy parser | Active |
| Static Analysis | Clippy -D warnings + Cargo Audit supply chain | Passing |
| Red Teaming | Python SDK hermetic test suite: prompt injection, payload malformation, policy bypass | Passing |
| Type Safety | Rust (memory safety) + mypy --strict (Python SDK) | Enforced |
View full SECURITY.md for proof harnesses and audit logs.
Performance
Micro-benchmarks (core logic):
- Codec decoding: ~247 ns / message
- Policy evaluation: ~660 ns / rule
End-to-end (Windows, release build, p50):
- RPC overhead: < 0.5 ms
- Startup: ~15 ms (process spawn + handshake)
- Memory: ~4 MB RSS
- Throughput: > 1.5 M validations/sec
Development
See docs/development.md for full setup.
# Rust core (from lilith-zero/)
cargo build && cargo test --all-features
cargo clippy --all-targets --all-features -- -D warnings
# Python SDK (from sdk/)
uv venv && uv pip install -e ".[dev]" && uv run pytest tests -v
License
Apache License, Version 2.0. See LICENSE.
