An open-source gateway for the AI coding agents and the web traffic on your machine, developed by Go and React.
English | 中文
One command. No database, no Go, no Node, no configuration.
On Linux and macOS:
curl -fsSL https://raw.githubusercontent.com/apache/casbin-gateway/master/scripts/install.sh | bashOn Windows, in PowerShell:
irm https://raw.githubusercontent.com/apache/casbin-gateway/master/scripts/install.ps1 | iexEither one downloads the build for this machine, unpacks it into ~/.local/share/casbin-gateway (%LOCALAPPDATA%\casbin-gateway on Windows), puts a casbin-gateway command on your PATH, and starts it. Then open:
http://localhost:17000 — sign in as admin with the password 123, and change it from My Account.
That is the whole installation. Gateway keeps its data in a SQLite file inside its own directory, and signs you in against its own user table.
| Page | What you get | What it needs |
|---|---|---|
| Agents | Every AI coding agent installed on this machine — Claude Code, Codex CLI, Cursor and more. Click Patch on one and its activity streams into the page live. | Nothing |
| Skills & MCP | Every skill and MCP server of every agent in one table. Add an MCP server to one agent or to several at once, open one, delete it, or copy it into another agent. | Nothing |
| Channels | One endpoint in front of your model vendors. Gateway holds the API key, so the agents never have it. | A vendor API key |
| LLM Records | Every request an agent relayed: the full system prompt, every message and tool call, the schema of every tool the model was offered, plus tokens and cost. | A channel, and llmRecordMode — see Recording prompts |
| Advanced → Sites | The reverse-proxy WAF: per-site routing, rules, certificates and analytics. | Turning the proxy on — see Turning the WAF proxy on |
Agents are found by reading the user accounts, home directories and install paths of the machine Gateway runs on, so run it on the machine whose agents you want to watch.
This is what fills LLM Records, and what lets Gateway keep the vendor key instead of the agent.
- Channels → Add: pick the type (OpenAI- or Anthropic-compatible), paste the vendor base URL and API key, and list the models it serves.
- Agents → open an agent → pick that channel.
- Copy the environment snippet the page shows, and start the agent from a shell that has it:
export ANTHROPIC_BASE_URL="http://localhost:17000/v1/agents/claude-code"
export ANTHROPIC_AUTH_TOKEN="casbin-gateway"The token is a placeholder — the agent refuses to start without one, and Gateway authenticates upstream with the channel's own key.
- Stop:
Ctrl-C. Start again:casbin-gateway, from any directory — the command is a wrapper that always starts Gateway in its install directory, where its data lives. - Upgrade: run the install command again. Your database and settings are untouched.
- Remove: delete
~/.local/share/casbin-gatewayand~/.local/bin/casbin-gateway(on Windows,%LOCALAPPDATA%\casbin-gatewayand its PATH entry).
Set INSTALL_DIR to install somewhere else, or NO_START=1 to install without starting.
These are nightly builds, rebuilt from master on every push and published as the nightly pre-release. They exist so that Gateway can be tried without a Go and Node toolchain; anything else should be built from a source release.
A container cannot see the agents on your machine. Agents are discovered by reading the home directories and install paths of the machine Gateway runs on, and inside a container that is the container's own filesystem. Agents, Skills & MCP and agent monitoring therefore stay empty there, and the pages say so rather than pretending nothing is installed. Everything that does not depend on the host works normally: Channels, LLM Records and the reverse-proxy WAF.
So run the one-command install above on the machine whose agents you want to watch, and use a container when Gateway is only a model endpoint or a reverse proxy for other machines.
No image is published, so the compose file builds one from a checkout of this repository:
docker compose up -dPodman reads the same file:
podman compose up -dEither way the UI is on http://localhost:17000, the SQLite database lives in a named volume that survives down, and conf/app.conf is mounted from the repository, so settings can be changed and the container restarted without rebuilding it.
To serve the WAF proxy from a container, set gatewayEnabled = true in that mounted conf/app.conf and publish its ports too, by adding them next to 17000:17000 in docker-compose.yml:
ports:
- "17000:17000"
- "8080:80"
- "8443:443"Everything is optional. Settings live in conf/app.conf, next to the executable, and each one is explained in the file itself. The ones people actually change:
| Setting | Default | What it does |
|---|---|---|
httpport |
17000 |
Port of the web UI and the REST API |
driverName / dataSourceName |
sqlite / ./data/casbin-gateway.db |
Where data is stored |
gatewayEnabled |
false |
Turns the reverse-proxy WAF on |
gatewayHttpPort / gatewayHttpsPort |
80 / 443 |
Ports the proxy listens on |
llmRecordMode |
off |
How much of each relayed LLM request is kept |
apiKeyEncryptionKey |
empty | Encrypts channel API keys at rest (AES-256-GCM) |
casdoorEndpoint |
empty | Switches sign-in over to Casdoor SSO |
Gateway prints what it is actually doing when it starts, so the result can be checked instead of the file:
+----------------------------------------------------------------------------+
| Casbin Gateway |
+----------------+-----------------------------------------------------------+
| Management UI | http://localhost:17000 |
| Settings | conf/app.conf |
| Web UI files | web/build |
| Reverse proxy | enabled |
| Gateway HTTP | :8080 |
| Gateway HTTPS | :8443 |
| Database | sqlite, file "./data/casbin-gateway.db" (connected) |
| Sign-in | built-in user table, Casdoor is not configured |
| App dir | ./data/apps |
+----------------+-----------------------------------------------------------+
If a port is taken, Gateway says which process holds it and stops, rather than starting half-configured.
Nothing about a relayed request is stored until you ask for it, because a prompt can carry anything that was pasted into it:
; "off" keeps nothing, "metadata" records who called which model with which
; outcome, "full" also stores the request body — which is what LLM Records needs
; to show prompts, messages and tool schemas.
llmRecordMode = "full"
llmRecordRetentionDays = 30
llmRecordMaxRecords = 10000
llmRecordMaxPayloadBytes = 1048576Bodies are sanitized before they are stored: anything that looks like a credential is replaced, and the number of replacements is shown with the record. Request headers, which is where the inbound API key is, never reach a record at all. A body over llmRecordMaxPayloadBytes keeps its structure and loses only its longest strings, so a large conversation is still listed message by message.
The cost next to each record uses built-in list prices, which vendors change and resellers do not follow. Point llmPricingFile at a JSON file of your own rates to correct them.
The reverse proxy is off by default, so installing Gateway does not take over ports 80 and 443. To use it:
- Advanced → Sites → Add. Set Domain to the hostname clients will use (
test.example.com), Host and Port to where the traffic goes (127.0.0.1and8000), and Mode toHTTP—HTTPS Only, the default, redirects plain HTTP away before it reaches the backend. - Set
gatewayEnabled = trueinconf/app.confand restart. Ports 80 and 443 need root on Linux and macOS, so for a first try setgatewayHttpPort = 8080. - Start anything on the backend port, e.g.
python -m http.server 8000, then ask for the site byHostheader — the gateway routes on it, so no DNS orhostsentry is needed:
curl -H "Host: test.example.com" http://127.0.0.1:8080/You should get your backend's response. A site not found for host reply means the request reached Gateway but no site matches that Host value.
Casdoor is optional and takes over member management. Create an organization and an application for Gateway in a Casdoor instance, then fill in casdoorEndpoint, clientId, clientSecret, casdoorOrganization and casdoorApplication. Sign-in redirects to Casdoor as soon as casdoorEndpoint is set, which also enables OAuth logins, health-check alerts, the CAPTCHA rule action, per-site SSO and cloud file storage.
Go 1.20+, and Node.js with Yarn.
The backend serves the compiled frontend out of web/build, so build it once first:
cd web && yarn install && yarn buildgo run main.goThen open http://localhost:17000 and sign in as admin with the password 123, same as an installed Gateway. The SQLite database is created on first start; there is no database server to install.
cd web && yarn devThat serves the UI on http://localhost:16002 with hot reload and proxies API calls to the backend on port 17000, so both have to be running.
XORM is used, so every database it supports works. Point Gateway at your server and it creates dbName on first start if it does not exist:
driverName = mysql
dataSourceName = root:123@tcp(localhost:3306)/
dbName = casbin_gatewayGateway normally reads three things from disk: conf/app.conf, the compiled UI in web/build, and the IP location database ip/17monipdb.dat. The embed build tag bakes all three into the executable, which is what the install scripts ship:
cd web && yarn install && yarn buildgo build -tags embed -o casbin-gateway .Build the frontend first — everything under web/build goes into the binary, so go build -tags embed fails to compile while that directory is missing.
Files on disk always win over the embedded copies, so a single binary can still be configured and developed against without rebuilding it:
| Embedded asset | Overridden by |
|---|---|
conf/app.conf |
conf/app.conf in the working directory, or next to the executable |
web/build |
web/build/index.html in the working directory, which then serves the whole UI |
ip/17monipdb.dat |
ip/17monipdb.dat in the working directory |
The startup summary reports which source each one came from.
Being self-contained is about startup, not about staying read-only. A running Gateway writes ./data (the SQLite database, deployed apps, agent patch state), ./logs and ./tmp relative to its working directory — which is why the installed casbin-gateway command is a wrapper that always starts it in its install directory. Running the executable directly from somewhere else gives you a second, empty installation there.
Casbin Gateway contains 2 parts:
| Name | Description | Language | Source code |
|---|---|---|---|
| Frontend | Web frontend UI for Casbin Gateway | TypeScript + React + shadcn/ui | https://github.com/apache/casbin-gateway/tree/master/web |
| Backend | RESTful API backend for Casbin Gateway | Golang + Beego + XORM | https://github.com/apache/casbin-gateway |
If you have any questions, open an issue, or start a pull request directly — though we recommend opening an issue first to talk it through with the community.