Brakes for your AI agents: every tool call is checked against your rules, held for human approval when risky, and recorded in a tamper-evident audit trail. Works with Claude Code and any MCP app.
Python
0
5 commits
updated Sep 29, 2026

Brakes for your AI agents. Every action an agent takes (running a command, editing a file, sending an email, issuing a refund, changing a database) goes through Squidbrake first. It is checked against your rules, held for a person when it's risky, recorded in a tamper-evident audit trail, and can be stopped instantly.
Free and open source (Apache 2.0). Runs on your laptop or your own server; your data never leaves it.
rules.yaml says what runs by itself, what's blocked, and what waits for a person.
No LLM in the decision path.acrne-corp.com pretending to be acme.com), flags duplicate refunds.finance approves wires), an emergency stop,
reports, CSV export and an audit trail you can verify.See SHOWCASE.md for a 5-minute demo with a sandbox company.

![]() An agent read an "urgent CEO" email from acrne-corp.com and tried to wire $24,800. Blocked, with the story of what led to it. | ![]() Approve or reject from your phone with one tap. |
Click the button and the live demo starts in your browser (free with a GitHub account): a sandbox company's AI
support agent works its inbox while you watch. A scam wire is blocked, refunds wait for a person, and a demo
manager approves or rejects them. On your own machine: pip install -r requirements.txt then python demo/live_demo.py.
git clone https://github.com/batrapulkit/squidbrake && cd squidbrake
./start.sh # Windows: start.bat
It installs itself, prints your keys and opens the dashboard. Then connect Claude Code (every tool call goes through Squidbrake from then on):
./connect.sh claude-code # Windows: connect.bat claude-code
Or with Docker: docker run -d -p 8080:8080 -v squidbrake-data:/app/data --name squidbrake ghcr.io/batrapulkit/squidbrake
(keys: docker logs squidbrake).
| Where | Command |
|---|---|
| Windows | double-click start.bat |
| macOS / Linux | ./start.sh |
| A Linux server, 24/7 | ./install.sh (or ./install.sh gateway.yourdomain.com for HTTPS) |
The first start installs everything, prints an admin key (for the dashboard) and an agent key
(shown once, so save them), and opens http://localhost:8080/dashboard. No configuration needed; every
setting in .env.example is optional.
Keys: python server.py add-key NAME [--approver], python server.py remove-key NAME, python server.py keys.
Changes apply immediately, no restart needed. (Inside Docker, prefix with docker compose exec gateway.)
With the gateway running, one command per agent (use the .venv Python that start.bat / start.sh created):
.venv/Scripts/python connect.py claude-code # Windows (macOS/Linux: .venv/bin/python)
.venv/Scripts/python connect.py mcp --name antigravity # also: claude-desktop, cursor
--project DIR to limit it to one project; --remove undoes it. If the gateway is down, Claude Code's
tool calls are blocked (fail closed) and it says why.list_tables, describe_table, query and execute tools on a SQLite database
(data/shop.db, created with sample customers / products / orders; set DB_PATH to use your own).
Reads run immediately, UPDATE/DELETE/INSERT/ALTER wait for your approval, and DROP/TRUNCATE are blocked.Things to ask the agent, then watch the dashboard:
git push waits for approval)cloudflared tunnel --url http://localhost:8080 prints a public
https://….trycloudflare.com link. Give viewers their own key: python server.py add-key guest.
Afterwards, press Ctrl+C and run python server.py remove-key guest../install.sh gateway.yourdomain.com on a small cloud server.Make a clean copy (no keys, no history): python pack.py -> dist/squidbrake.zip.
start.bat (Windows) or run ./start.sh. It makes its own keys.connect.bat wrap --sandbox --agent claude-code --url https://your-gateway --key gw_...
(or connect.bat claude-code --url ... --key ... to route every Claude Code action through your gateway).bash install.sh (HTTPS included, no domain needed).Python - wrap your tools:
from client import Gateway, Denied
gw = Gateway("https://gateway.example.com", api_key="...", source="my-agent", session_id=run_id)
@gw.guard(name="shell.exec")
def shell_exec(command: str): ...
Any language - two HTTP calls (header X-Gateway-Key: <secret>):
POST /v1/events {"name": "shell.exec", "input": {...}, "source": "...", "session_id": "..."}
-> {"event_id": "...", "decision": "allow" | "deny", "reason": "...", "rule_id": ...}
POST /v1/events/{id}/result {"output": ..., "error": null, "duration_ms": 12}
Record an action that already happened in one call by including output/error in the first POST.
HTTP proxy - no code changes: define upstreams in rules.yaml, then point the client at
http://gateway:8080/proxy/<upstream>/.... Optional headers: X-Gateway-Source, X-Gateway-Session.
Denied requests get 403; every response carries X-Gateway-Event-Id.
Rules with action: review hold the call until a person approves or rejects it:
- id: approve-payments
action: review
reason: Money movement needs a human
timeout_seconds: 600 # default APPROVAL_TIMEOUT (300)
on_timeout: deny # or allow
approvers: [alice, bob] # optional; default = any approver key
match: { name: "payments.*" }
POST /v1/events returns "decision": "review". The Python client handles this for
you: check() and @gw.guard block until the call is decided, then run the tool or raise Denied.
Other clients long-poll GET /v1/events/{id}/decision?wait=25 until decision is allow or deny.POST /v1/events/{id}/approve / .../reject with an optional {"note": "..."}.python server.py add-key NAME --approver, and the rule's approvers if set) can decide,
and a key can never approve its own request. So give people their own keys, separate from the agents' keys.on_timeout applies and decided_by is recorded as timeout.APPROVAL_WEBHOOK_URL (plus PUBLIC_URL) to get a Slack-style message with a link to the event
whenever something needs approval.timeout_seconds.Open http://<host>:8080/dashboard and paste your admin key (or any key made with add-key). It is stored
only in that browser. The page shows:
/dashboard?status=denied&range=7d can be bookmarked or sharedClaude Code's built-in permissions control actions for an individual agent. Squidbrake provides centralized rules for a whole team, with approvals from your phone or Slack, history checks, an audit trail, and support across multiple agents.
Squidbrake fails closed. Guarded tool calls are blocked if the gateway is unreachable, rather than being allowed through.
No. Squidbrake is self-hosted and runs on your laptop or your own server. Your data stays in your environment.
No. Squidbrake uses deterministic rules and checks. There is no LLM in the decision path.
An agent cannot bypass Squidbrake through tools that are connected to the gateway. An agent could still use a tool that isn't connected to Squidbrake. See SECURITY.md for the security model and limitations.
| Endpoint | |
|---|---|
GET /v1/events?q=&session_id=&source=&client=&kind=&name=&status=&since=&before=&limit= | newest first, page with before=<next_before> |
GET /v1/events/{id} | one event |
GET /v1/events/{id}/decision?wait=25 | current decision; long-polls while awaiting approval |
POST /v1/events/{id}/approve · /reject | human decision, body {"note": "..."} (approver keys only) |
GET /v1/me | which key you are and whether it can approve |
GET /v1/stats?hours=24&bucket=hour | counts by status, top tool names, timeline (same filters as events) |
POST /v1/policy/check | dry-run a call against the rules (not recorded) |
GET /docs | interactive OpenAPI docs |
fail_open=True to change that).password, token, api_key, authorization, cookie and values like
Bearer ..., sk-..., ghp_... are stored as [REDACTED]. Rules still see the raw input.MAX_PAYLOAD_CHARS are truncated in storage. Proxy responses are buffered (no streaming/SSE).DATABASE_URL to Postgres.pip install pytest && pytest -q
python tests/e2e_business_scenario.py
Python
61.9%
HTML
34.3%
Shell
2.7%
Brakes for your AI agents: every tool call is checked against your rules, held for human approval when risky, and recorded in a tamper-evident audit trail. Works with Claude Code and any MCP app.
Python
0
5 commits
updated Sep 29, 2026

Brakes for your AI agents. Every action an agent takes (running a command, editing a file, sending an email, issuing a refund, changing a database) goes through Squidbrake first. It is checked against your rules, held for a person when it's risky, recorded in a tamper-evident audit trail, and can be stopped instantly.
Free and open source (Apache 2.0). Runs on your laptop or your own server; your data never leaves it.
rules.yaml says what runs by itself, what's blocked, and what waits for a person.
No LLM in the decision path.acrne-corp.com pretending to be acme.com), flags duplicate refunds.finance approves wires), an emergency stop,
reports, CSV export and an audit trail you can verify.See SHOWCASE.md for a 5-minute demo with a sandbox company.

![]() An agent read an "urgent CEO" email from acrne-corp.com and tried to wire $24,800. Blocked, with the story of what led to it. | ![]() Approve or reject from your phone with one tap. |
Click the button and the live demo starts in your browser (free with a GitHub account): a sandbox company's AI
support agent works its inbox while you watch. A scam wire is blocked, refunds wait for a person, and a demo
manager approves or rejects them. On your own machine: pip install -r requirements.txt then python demo/live_demo.py.
git clone https://github.com/batrapulkit/squidbrake && cd squidbrake
./start.sh # Windows: start.bat
It installs itself, prints your keys and opens the dashboard. Then connect Claude Code (every tool call goes through Squidbrake from then on):
./connect.sh claude-code # Windows: connect.bat claude-code
Or with Docker: docker run -d -p 8080:8080 -v squidbrake-data:/app/data --name squidbrake ghcr.io/batrapulkit/squidbrake
(keys: docker logs squidbrake).
| Where | Command |
|---|---|
| Windows | double-click start.bat |
| macOS / Linux | ./start.sh |
| A Linux server, 24/7 | ./install.sh (or ./install.sh gateway.yourdomain.com for HTTPS) |
The first start installs everything, prints an admin key (for the dashboard) and an agent key
(shown once, so save them), and opens http://localhost:8080/dashboard. No configuration needed; every
setting in .env.example is optional.
Keys: python server.py add-key NAME [--approver], python server.py remove-key NAME, python server.py keys.
Changes apply immediately, no restart needed. (Inside Docker, prefix with docker compose exec gateway.)
With the gateway running, one command per agent (use the .venv Python that start.bat / start.sh created):
.venv/Scripts/python connect.py claude-code # Windows (macOS/Linux: .venv/bin/python)
.venv/Scripts/python connect.py mcp --name antigravity # also: claude-desktop, cursor
--project DIR to limit it to one project; --remove undoes it. If the gateway is down, Claude Code's
tool calls are blocked (fail closed) and it says why.list_tables, describe_table, query and execute tools on a SQLite database
(data/shop.db, created with sample customers / products / orders; set DB_PATH to use your own).
Reads run immediately, UPDATE/DELETE/INSERT/ALTER wait for your approval, and DROP/TRUNCATE are blocked.Things to ask the agent, then watch the dashboard:
git push waits for approval)cloudflared tunnel --url http://localhost:8080 prints a public
https://….trycloudflare.com link. Give viewers their own key: python server.py add-key guest.
Afterwards, press Ctrl+C and run python server.py remove-key guest../install.sh gateway.yourdomain.com on a small cloud server.Make a clean copy (no keys, no history): python pack.py -> dist/squidbrake.zip.
start.bat (Windows) or run ./start.sh. It makes its own keys.connect.bat wrap --sandbox --agent claude-code --url https://your-gateway --key gw_...
(or connect.bat claude-code --url ... --key ... to route every Claude Code action through your gateway).bash install.sh (HTTPS included, no domain needed).Python - wrap your tools:
from client import Gateway, Denied
gw = Gateway("https://gateway.example.com", api_key="...", source="my-agent", session_id=run_id)
@gw.guard(name="shell.exec")
def shell_exec(command: str): ...
Any language - two HTTP calls (header X-Gateway-Key: <secret>):
POST /v1/events {"name": "shell.exec", "input": {...}, "source": "...", "session_id": "..."}
-> {"event_id": "...", "decision": "allow" | "deny", "reason": "...", "rule_id": ...}
POST /v1/events/{id}/result {"output": ..., "error": null, "duration_ms": 12}
Record an action that already happened in one call by including output/error in the first POST.
HTTP proxy - no code changes: define upstreams in rules.yaml, then point the client at
http://gateway:8080/proxy/<upstream>/.... Optional headers: X-Gateway-Source, X-Gateway-Session.
Denied requests get 403; every response carries X-Gateway-Event-Id.
Rules with action: review hold the call until a person approves or rejects it:
- id: approve-payments
action: review
reason: Money movement needs a human
timeout_seconds: 600 # default APPROVAL_TIMEOUT (300)
on_timeout: deny # or allow
approvers: [alice, bob] # optional; default = any approver key
match: { name: "payments.*" }
POST /v1/events returns "decision": "review". The Python client handles this for
you: check() and @gw.guard block until the call is decided, then run the tool or raise Denied.
Other clients long-poll GET /v1/events/{id}/decision?wait=25 until decision is allow or deny.POST /v1/events/{id}/approve / .../reject with an optional {"note": "..."}.python server.py add-key NAME --approver, and the rule's approvers if set) can decide,
and a key can never approve its own request. So give people their own keys, separate from the agents' keys.on_timeout applies and decided_by is recorded as timeout.APPROVAL_WEBHOOK_URL (plus PUBLIC_URL) to get a Slack-style message with a link to the event
whenever something needs approval.timeout_seconds.Open http://<host>:8080/dashboard and paste your admin key (or any key made with add-key). It is stored
only in that browser. The page shows:
/dashboard?status=denied&range=7d can be bookmarked or sharedClaude Code's built-in permissions control actions for an individual agent. Squidbrake provides centralized rules for a whole team, with approvals from your phone or Slack, history checks, an audit trail, and support across multiple agents.
Squidbrake fails closed. Guarded tool calls are blocked if the gateway is unreachable, rather than being allowed through.
No. Squidbrake is self-hosted and runs on your laptop or your own server. Your data stays in your environment.
No. Squidbrake uses deterministic rules and checks. There is no LLM in the decision path.
An agent cannot bypass Squidbrake through tools that are connected to the gateway. An agent could still use a tool that isn't connected to Squidbrake. See SECURITY.md for the security model and limitations.
| Endpoint | |
|---|---|
GET /v1/events?q=&session_id=&source=&client=&kind=&name=&status=&since=&before=&limit= | newest first, page with before=<next_before> |
GET /v1/events/{id} | one event |
GET /v1/events/{id}/decision?wait=25 | current decision; long-polls while awaiting approval |
POST /v1/events/{id}/approve · /reject | human decision, body {"note": "..."} (approver keys only) |
GET /v1/me | which key you are and whether it can approve |
GET /v1/stats?hours=24&bucket=hour | counts by status, top tool names, timeline (same filters as events) |
POST /v1/policy/check | dry-run a call against the rules (not recorded) |
GET /docs | interactive OpenAPI docs |
fail_open=True to change that).password, token, api_key, authorization, cookie and values like
Bearer ..., sk-..., ghp_... are stored as [REDACTED]. Rules still see the raw input.MAX_PAYLOAD_CHARS are truncated in storage. Proxy responses are buffered (no streaming/SSE).DATABASE_URL to Postgres.pip install pytest && pytest -q
python tests/e2e_business_scenario.py
Python
61.9%
HTML
34.3%
Shell
2.7%