Skip to main content

AgentGate

One-line firewall for AI coding agents. Intercepts every tool call, network request, and human approval — let Claude Code / Cursor / Codex touch what you allow, block what you don't, ask for the rest.

AgentGate sits between your AI coding agent and the rest of your system. It ships as three small pieces you wire up in 60 seconds:

  1. agentgate install-hook — registers a PreToolUse hook with Claude Code (or any MCP-compatible agent) that intercepts Bash / Read / Write / Edit / WebFetch / Grep / Glob calls.
  2. agentgate proxy — a mitmproxy add-on that intercepts every outbound HTTP(S) request and applies your domain allow/deny list.
  3. agentgate dashboard — a single-page HTML viewer for the audit log, with live stats, time series, and per-rule breakdowns.

When an ask action fires, AgentGate posts to a Slack incoming webhook and waits (up to 60s by default) for a human to click Allow or Deny at http://your-host:8765/approve/<token>.


Quick start

# 1. Install
git clone https://github.com/FelixMa01/agentgate && cd agentgate
uv sync

# 2. Init a policy + audit DB in your project
cd ~/code/my-project
agentgate init --dir .                 # writes ./policy.yaml + ./audit.db

# 3. Wire it into Claude Code (writes .claude/settings.local.json)
agentgate install-hook \
    --policy ./policy.yaml \
    --db ./audit.db \
    --target .                         # project scope (or --scope user for all projects)

# 4. (optional) Start the network proxy
agentgate proxy -p ./policy.yaml --db ./audit.db   # listens on :8080
export HTTP_PROXY=http://127.0.0.1:8080
export HTTPS_PROXY=http://127.0.0.1:8080

# 5. (optional) Start the approval server (for ASK actions)
agentgate approval-server --port 8765

# 6. Watch the dashboard
agentgate dashboard --db ./audit.db   # http://127.0.0.1:8766

Policy format (policy.yaml)

version: 1
default: allow       # what to do when no rule matches

rules:
  # Match glob-style — * matches across slashes
  - id: deny-rm-rf
    name: Block destructive rm
    match:
      tool: Bash
      command: "rm -rf /*"
    action: deny
    reason: "Mass deletion outside repo"

  # Match a list of patterns — any match fires
  - id: deny-secrets
    name: Block reading secrets
    match:
      tool: Read
      file_glob: ["*.pem", ".env*", "*id_rsa*"]
    action: deny

  # Use ~regex~ prefix to drop into regex land
  - id: ask-outbound
    name: Require approval for outbound network
    match:
      tool: Bash
      command: "~\bcurl\b|\bwget\b"
    action: ask
    reason: "Outbound network from agent"

network:
  allowed_domains:
    - github.com
    - "*.pypi.org"
    - openai.com
    - "*.openai.com"
  denied_domains:
    - pastebin.com
    - "*.transfer.sh"
  require_https: true

Actions

Action What happens
allow Tool runs. Event logged.
deny Tool blocked. Event logged. User sees the reason in the Claude Code transcript.
ask Slack message sent. Hook blocks until a human clicks Allow/Deny (or timeout → deny by default).
log Tool runs, but the event is recorded even without matching an explicit rule.

Pattern matching

  • Plain strings are exact matches.
  • Glob (*, ?) patterns use Python's fnmatch.translate, so * does match /.
  • A leading ~ switches to regex mode: "~\\brm\\s+-rf\\b".
  • Patterns can be a list — any match fires.
  • Suffix _glob on a key (e.g. file_glob) tries the base name (file) if the suffixed key isn't in the event.

Architecture

   ┌──────────────────────────┐
   │   Claude Code / Codex    │
   │   (Bash, Read, Write,    │
   │    Edit, WebFetch, ...)  │
   └────────────┬─────────────┘
                │ tool call
                ▼
   ┌──────────────────────────┐         ┌─────────────────┐
   │  bin/agentgate-hook.py   │ ──────▶ │  policy.yaml    │
   │  (PreToolUse, JSON stdin │         │  + SQLite audit │
   │   JSON stdout response)  │ ◀────── │  (events table) │
   └────────────┬─────────────┘         └─────────────────┘
                │ if ask → Slack + wait
                ▼
   ┌──────────────────────────┐
   │  approval server :8765   │
   │  GET /approve/<token>?d= │
   └──────────────────────────┘

   ┌──────────────────────────┐         ┌─────────────────┐
   │  mitmproxy add-on :8080  │ ──────▶ │  policy.yaml    │
   │  (every HTTP request)    │         │  network section│
   └──────────────────────────┘         └─────────────────┘

The hook, proxy, and approval server all read the same policy.yaml and write to the same audit.db. The dashboard reads only the DB.


CLI

agentgate init                       # scaffold a default policy.yaml + audit.db
agentgate eval -p policy.yaml --db audit.db --event-json '{"tool":"Bash","command":"x"}'
agentgate audit --db audit.db --limit 20
agentgate stats --db audit.db
agentgate validate -p policy.yaml

agentgate install-hook -p policy.yaml --db audit.db --target .
agentgate uninstall-hook --target .

agentgate proxy -p policy.yaml --db audit.db --listen-port 8080
agentgate approval-server --port 8765
agentgate dashboard --db audit.db --port 8766

agentgate ask-test -p policy.yaml --db audit.db --event-json '{...}'

Slack approval flow

Set AGENTGATE_SLACK_WEBHOOK=https://hooks.slack.com/services/... and AgentGate will post a Block Kit message with Allow / Deny buttons. Without a webhook, it falls back to writing the JSON to /tmp/agentgate-asks.jsonl so you can wire your own channel.

When the human clicks Allow / Deny, the request hits http://your-host:8765/approve/<token>?d=allow. The approval server writes the decision into the SQLite approvals table; the waiting hook (which may be a different process) sees the update via in-process notification + DB poll fallback.

If no human responds within AGENTGATE_ASK_TIMEOUT (default 60s), the hook denies by default — fail-closed.


Why not just use the agent's built-in permissions?

You can — but AgentGate gives you:

  1. Network egress control. Built-in permissions don't see what your agent calls over the wire.
  2. Centralized policy. One YAML for the whole team, instead of scattered .claude/settings.json files.
  3. Cross-tool unified audit. One DB for every tool, hook, and proxy — searchable, time-seriesed, shareable.
  4. Human-in-the-loop without leaving your chat. Slack approve/deny buttons beat terminal prompts.
  5. Postmortem-friendly. Every decision has a rule_id, reason, and full event payload.

Status

🟢 Day 5 shipped. See /api/stats on the dashboard for live numbers.

Day Deliverable Status
1 Policy DSL + SQLite audit + CLI
2 Claude Code PreToolUse hook + install/uninstall
3 mitmproxy add-on for network egress
4 Slack approval + cross-process HTTP server
5 Single-page HTML dashboard + Product Hunt launch

48 unit tests passing. End-to-end verified on macOS (Apple Silicon, Python 3.13 via uv).

License

Apache 2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agentgate_firewall-0.2.0.tar.gz (36.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentgate_firewall-0.2.0-py3-none-any.whl (34.7 kB view details)

Uploaded Python 3

File details

Details for the file agentgate_firewall-0.2.0.tar.gz.

File metadata

  • Download URL: agentgate_firewall-0.2.0.tar.gz
  • Upload date:
  • Size: 36.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for agentgate_firewall-0.2.0.tar.gz
Algorithm Hash digest
SHA256 72cc586715ce72d27e5a17fd567add33ee776819edb5d5f44c3fd8b9865640cf
MD5 89575017614caba47c1ac13367bdc70c
BLAKE2b-256 e608d107cb8e40ac14615ed7c3f6eac3228b2738ca5584e5cb85e6aca4476bc3

See more details on using hashes here.

File details

Details for the file agentgate_firewall-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: agentgate_firewall-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 34.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for agentgate_firewall-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a9d2812321a7ca360e25846370a7e3fcd81eefa046a8be857a959632ed5ae49f
MD5 07fa4cf9f194658e719f5c3257242df6
BLAKE2b-256 4d547a3d49cbca32d1657af0d58023b12f187b8eff59eddf4c4c9bda19580112

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page