Skip to main content

Leash – Keep your AI agents on a leash

PyPI Tests Docker License Python 3.11+

Leash

Keep your AI agents on a leash.

You wouldn't let a dog roam the neighborhood unsupervised, so why let an AI agent read your files, send emails, and call APIs without guardrails? Leash is an API-layer policy engine that sits between your agent and the outside world — no containers, no sidecars, just authorization. You write simple YAML rules that say what's allowed. Everything else is denied. Every decision from allow or deny activities is logged in a cryptographically signed, hash-chained audit trail that's tamper-evident by design.

One pip install, one policy file, and your agent is on a leash.

🌟 Highlights

  • 🐕 Deny by default — nothing happens unless your policy says so
  • 📜 YAML rules — human-readable, version-controllable, git-diffable, hot-reloaded
  • 🔗 Tamper-evident audit trail — every decision is RSA-signed and SHA-256 hash-chained; edits and deletions are detectable
  • 👀 Observe mode — shadow new rules in production before enforcing
  • 🔍 Security scanner — discover an MCP server's tools, classify risk, generate policies
  • 🔒 Hardened by default — admin-only policy management, admin-key-gated admin agents, fail-closed revocation
  • 🧩 Framework-agnostic — Python SDK, MCP proxy, OpenClaw plugin, or plain REST
  • 🧠 OpenClaw ready — a before_tool_call plugin plus a built-in policy for the popular open-source AI assistant
  • 🛡️ OWASP mapped — rules and audit checks reference OWASP ASI and LLM Top 10 threat IDs
  • ⚡ Pure Python — pip install leash. No Go, no Rust, no sidecar containers

⚙️ How It Works

Before your agent runs a tool, it asks Leash "can I do this?":

 Agent / MCP client / OpenClaw
            │  POST /authorize  {agent_id, action, resource, context}  + JWT
            ▼
 ┌─────────────────────── Leash server ───────────────────────┐
 │  Identity (RS256 JWT)  →  Policy engine (YAML + managed)   │
 │                              │                             │
 │                              ▼                             │
 │            Audit log (signed, hash-chained, SQLite)        │
 └──────────────────────────────┬─────────────────────────────┘
            ▼
     allow → tool runs          deny → tool never runs
  1. Identity — each agent registers once and gets a UUID and a signed JWT.
  2. Policy — the engine matches the agent's name against policy agents: patterns, then the first matching rule by priority decides. No match → deny.
  3. Audit — every decision (allow, deny, observe-deny) is signed and linked to the previous entry's hash, so tampering breaks the chain.

If Leash is unreachable, the SDK, MCP proxy, and OpenClaw plugin all fail closed.

⬇️ Install and see it work (60 seconds)

git clone https://github.com/chadeckles/leash.git && cd leash
python3 -m venv .venv && source .venv/bin/activate
pip install -e .           # makes the `leash` command available
leash demo                 # 3 allows, 3 denies, audit log, tamper detection
leash scan openclaw        # what can each OpenClaw tool do, and is it allowed?

leash demo needs no server, config, or API keys. It starts a throwaway Leash in a temp directory, sends six real tool calls from an "OpenClaw" agent, shows each decision and reason, prints the hash-chained audit log, then edits one entry and shows the chain verification catch it. Add --step to pause between parts (great for presenting) or --keep to leave it running and open the dashboard.

Then change exec: deny to exec: allow in app/policies/openclaw.yaml and run leash demo again — the shell command is now allowed and leash scan openclaw flags it as high risk and allowed. Change it back when you're done.

Requires Python 3.11+ (macOS ships with 3.9 — run brew install python@3.12 first if needed). pip install leash also works, but the PyPI release can lag behind main.

🚀 Quickstart Workflow

# 1. Start the server (terminal 1)
leash start                      # http://localhost:8000  (--port, --reload)

# 2. Register an agent (terminal 2) — the name is what policies match on
leash agents register --name openclaw-agent

# 3. See what it may do, then test real decisions
leash agents permissions openclaw-agent
leash policy test --agent openclaw-agent -a read -a web_search -a exec

# 4. Inspect the evidence
leash audit log                  # ✔/✘ per decision
leash audit scan                 # integrity, suspicious chains, deny storms, gaps
leash dashboard                  # live terminal UI (web UI: /dashboard)

The first CLI command auto-registers a cli-admin identity using the server's admin key (.keys/admin.key, or LEASH_ADMIN_KEY) and caches it in ~/.leash/token.json. Agent identities are saved to ~/.leash/<name>.json with 0600 permissions.

💡 Deny by default. Anything no policy allows is denied. That's the point — nothing runs unless your rules say so.

🔌 Integrations

Python SDK

For agents written in Python (LangChain, CrewAI, custom code):

from sdk import LeashAgent

agent = LeashAgent("http://localhost:8000", name="my-agent")

@agent.tool("email.read")
def read_inbox(mailbox: str):
    return gmail.read(mailbox)

with agent:
    read_inbox("user@example.com")   # Leash checks permission first

Wrap many tools at once with guard() — works with plain callables and LangChain tools:

guarded = agent.guard([read_inbox, send_email, summarize, search])

A denied call never runs (LeashDenied by default; see on_deny). If the server is unreachable, the SDK denies. Expired tokens refresh automatically; revoked tokens raise LeashRevoked. See the SDK Reference.

MCP Proxy (Claude Desktop, Cursor, etc.)

MCP has no built-in authorization. The proxy sits between the client and any MCP server — no server changes:

python -m sdk.mcp_proxy \
    --agent-name "fs-agent" \
    -- npx -y @modelcontextprotocol/server-filesystem /data

Every tools/call is authorized. Resource-like arguments (path, source, destination, paths[], …) are each checked against resource: rules, scalar arguments are available to conditions as arg.<name>, and tools whose description or schema changes mid-session are blocked (tool-poisoning defense). See the MCP Proxy Guide.

OpenClaw

OpenClaw can run shell commands, browse the web, and edit files. The leash-gate plugin hooks OpenClaw's before_tool_call so every tool call is authorized by Leash, and denied calls are blocked with the policy's reason:

leash start                                  # terminal 1
python3 integrations/openclaw/lab.py setup   # terminal 2: registers the agent, installs + enables the plugin, configures the gateway
openclaw gateway run                         # terminal 2
python3 integrations/openclaw/lab.py allow   # terminal 3: 3 allowed tool calls
python3 integrations/openclaw/lab.py deny    # terminal 3: 3 blocked tool calls

The built-in openclaw.yaml policy is a list of tool: allow|deny toggles: it allows reads, web search, and memory, and denies exec, write, browser, and other high-risk tools. Run leash scan openclaw to see every tool, its risk, and its current decision. See the OpenClaw Integration Guide or the hands-on OpenClaw Lab.

REST API

Any language — register once, then ask before each action:

RESP=$(curl -s -X POST http://localhost:8000/agents \
  -H "Content-Type: application/json" -d '{"name": "my-agent"}')
AGENT_ID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['agent_id'])")
TOKEN=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

curl -s -X POST http://localhost:8000/authorize \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"agent_id": "'$AGENT_ID'", "action": "email.read", "resource": "inbox"}'

Interactive API docs live at http://localhost:8000/docs.

📜 Writing Rules

Rules live in app/policies/*.yaml (or POLICIES_DIR) and are hot-reloaded — no restart needed.

Simple format — flip allow / deny. This is all app/policies/openclaw.yaml is:

agent: openclaw          # which agent(s) this applies to (name contains "openclaw")

tools:
  read: allow            # 🟢 can read files in its workspace
  write: deny            # 🔴 can create or overwrite any file it can reach
  exec: deny             # 🔴 can run any shell command on your computer
  browser: deny          # 🔴 can drive a real web browser
  web_search: allow      # 🟢 can search the web

everything_else: deny    # tools not listed above

Not sure what a tool does? leash scan openclaw lists every OpenClaw tool, what it can do, its risk, and what your policy currently decides. Misspell a tool and leash policy validate says "did you mean 'exec'?".

Full format — for anything more. Resources, conditions, rate limits, and observe mode use the full rule list (the simple format compiles into this; you can also add a rules: list to a simple file, and those rules run first):

name: email-agent
priority: 10
mode: enforce          # or "observe" to log would-be denials without blocking
agents: ["*email*"]
rules:
  - action: "email.read"
    effect: allow
    reason: "Agent may read emails"

  - action: "email.send"
    effect: allow
    reason: "Agent may send, but slowly"
    rate_limit: { max_calls: 10, window: 3600 }

  - action: "email.*"
    effect: deny
    reason: "Everything else is blocked"
  • priority — higher is evaluated first. default.yaml (priority 0) is the catch-all deny; use 10+ for your policies.
  • agents — wildcard patterns matched against the agent's name. Entries shaped like UUIDs match only that exact agent_id.
  • rules — first match wins. Optional resource, conditions (ABAC on request context), rate_limit, and owasp tags.

Validate before you ship, and dry-run a candidate file against a real agent:

leash policy validate app/policies/
leash policy test --agent my-agent -a email.send -f candidate.yaml

Observe first. Deploy new policies with mode: observe, review leash audit log --decision observe_deny, then flip to enforce. See Write Your First Policy.

Policies can also be managed over the API (/policies/managed). By default only admin identities may create them; non-admin agents may only create self-restricting, deny-only policies scoped to themselves.

🔗 Tamper-Evident Audit

Every /authorize decision is written to the audit log with an RSA signature and the SHA-256 hash of the previous entry. Changing or deleting any entry breaks the chain for everything after it:

curl -s http://localhost:8000/verify/audit-chain
# {"valid": true, "entries_checked": 42, "detail": "Hash chain intact across 42 entries."}

leash audit verify                                 # ✔ VALID / ✘ BROKEN (exit 1)
leash audit scan                                   # 🔴 CRITICAL if the chain is broken
leash audit export --since 24h > audit.jsonl       # SIEM-friendly JSONL

Stream events elsewhere as they happen with LEASH_WEBHOOK_URL (HTTP POST) or LEASH_AUDIT_SINK (append-only JSONL file).

🔐 Security Model

Control Default
Policy management (/policies/managed) Admin only (LEASH_POLICY_REQUIRE_ADMIN=true)
Registering cli / admin / ops agents Requires admin JWT or X-Leash-Admin-Key
Admin key LEASH_ADMIN_KEY, else auto-generated at KEYS_DIR/admin.key (0600)
Agent registration Open; set LEASH_REQUIRE_AUTH_REGISTER=true to require admin
Read endpoints (metrics, overview, export) Open; set LEASH_REQUIRE_AUTH_READ=true for network-exposed deployments
Token revocation Deleted agents and key rotation revoke tokens; DB errors fail closed (503)
Identity One JWT per agent; an agent can only authorize its own agent_id

Rotate server keys with leash server rotate-keys. See SECURITY.md and Architecture.

Configuration

Variable Default Purpose
DATABASE_URL sqlite:///<repo>/leash.db Database location
POLICIES_DIR <repo>/app/policies YAML policy directory
KEYS_DIR <repo>/.keys Server keys and admin.key
JWT_EXPIRATION_HOURS 168 Agent token lifetime
LEASH_ADMIN_KEY auto-generated Admin bootstrap key
LEASH_POLICY_REQUIRE_ADMIN true Admin-only policy management
LEASH_REQUIRE_AUTH_REGISTER false Require admin to register agents
LEASH_REQUIRE_AUTH_READ false Require auth for read endpoints
LEASH_WEBHOOK_URL / LEASH_AUDIT_SINK — Real-time audit export
LEASH_CORS_ORIGINS localhost:8000 Allowed browser origins
LEASH_URL http://localhost:8000 Server URL used by the CLI

🔍 CLI Cheat Sheet

leash start [--port 8000] [--reload]      # run the server
leash demo [--step] [--keep]              # 60-second offline demo
leash status                              # server health + metrics
leash doctor                              # health-check your deployment

leash agents register --name my-bot       # register (token → ~/.leash/my-bot.json)
leash agents list | show | permissions | delete <name-or-id>

leash policy list                         # YAML + managed policies
leash policy validate app/policies/       # lint YAML rules
leash policy test --agent my-bot -a exec  # live decision (add -f file.yaml for dry-run)

leash audit log [--decision deny]         # recent decisions
leash audit summary                       # allow/deny stats
leash audit scan                          # integrity, chains, deny storms, shadows, gaps
leash audit verify                        # re-check the hash chain
leash audit export --since 24h            # JSONL export

leash scan openclaw                       # OpenClaw tools: what they do, risk, allowed?
leash scan -- npx -y @modelcontextprotocol/server-filesystem /data   # MCP tool risk scan
leash dashboard                           # live terminal UI
leash server rotate-keys                  # rotate server signing keys

Full details: CLI Reference.

🐳 Docker

make docker-up     # build + start on 127.0.0.1:8000 (auth hardening enabled)
make docker-down   # stop + remove volumes

# Point the host CLI at the container's admin key:
export LEASH_ADMIN_KEY=$(docker exec leash-server cat /app/.keys/admin.key)

Images are published to GHCR, multi-arch (amd64 + arm64), and signed with cosign.

🏗️ Project Layout

app/                     ← Leash server (FastAPI)
  main.py                ← app, /health, /metrics, /dashboard, /admin/*
  routes/                ← /agents, /authorize, /policies, /audit, /scan, /verify
  policies/              ← YAML rules (edit these) — default, demo, email, openclaw
  policy/                ← policy engine, validator, simple toggle format, OpenClaw tool catalog
  audit/                 ← signed, hash-chained audit log, scans, export dispatch
  identity/              ← agent registration, JWT issuance, key rotation
  core/                  ← config, auth, crypto, database, metrics
  models/                ← SQLAlchemy models (agents, policies, audit_log)
  static/dashboard.html  ← web dashboard
sdk/
  client.py              ← Python SDK (LeashAgent, LeashDenied, LeashRevoked)
  cli.py                 ← `leash` CLI
  mcp_proxy.py           ← MCP authorization proxy
  scanner.py             ← MCP tool-surface scanner
  scan_openclaw.py       ← `leash scan openclaw`
  dashboard.py           ← terminal dashboard
  demo.py                ← `leash demo` offline walkthrough
integrations/
  openclaw/leash-gate/   ← OpenClaw before_tool_call plugin
  openclaw/lab.py        ← one-command OpenClaw setup + allow/deny lab
docs/docs/               ← guides and references (MkDocs)
scripts/                 ← helper scripts
tests/                   ← pytest suite

📖 Documentation

Guide
Getting Started Install and first decisions
Write Your First Policy Observe → scan → enforce workflow
OpenClaw Integration · OpenClaw Lab Govern an OpenClaw assistant
MCP Proxy Guide Claude Desktop, Cursor, any MCP server
SDK Reference · CLI Reference API details
Architecture How it works under the hood

🧪 Development

pip install -e ".[dev]"
make test          # pytest
make lint          # ruff
make dev           # server with auto-reload

✍️ Author

Built by Chad Eckles.

💭 Feedback & Contributing

Found a bug? Have an idea? Open an issue or check out the contributing guide.

📄 License

Apache 2.0 — see LICENSE.

Metadata

Release files for leash 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for leash 0.4.0
File Size Uploaded
leash-0.4.0.tar.gz 1.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for leash 0.4.0
File Interpreter ABI Platform
leash-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.3 MB

Release files / leash-0.4.0.tar.gz

Download URL leash-0.4.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
e304502b0be2a497f6fa78836f8b32f8296a0e728166bb98755270bfddf2f2a3
BLAKE2b-256 checksum
How to use checksums
652ba1e31bbf870bad020ae7d4db1d408232690764a6d691c88c6e415cd07f2c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / leash-0.4.0-py3-none-any.whl

Download URL leash-0.4.0-py3-none-any.whl
Size 125.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
15425cef322113f058e462dcb4b4aa96791463ebe8b9363681b6c5a282699350
BLAKE2b-256 checksum
How to use checksums
b511d05b08a61575e7fe164baf680de9669ff95dd00951b81cfc3dcbe754b1ba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.1

2 release files

This release

0.4.0 This release

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page