{"slug": "casbin-gateway-a-security-gateway-for-the-ai-coding-agents-on-your-machine", "title": "Casbin Gateway: a security gateway for the AI coding agents on your machine", "summary": "Apache released Casbin Gateway, a local security gateway that installs with a single command and runs on http://localhost:17000 to govern the AI coding agents on a machine. The tool compiles roughly forty switches over each agent's tools, models and providers into a Casbin policy enforced on every relayed request, supports 44 vendor presets plus any OpenAI- or Anthropic-compatible base URL, and tracks requests, tokens, cache hit rate and cost by model. It manages agents including Claude Code, Codex CLI, Gemini CLI, Cursor, Windsurf, Cline and Qwen Code, storing data in a local SQLite file with default credentials admin/123 that only matter if the gateway is exposed to the network.", "body_md": "**English** | [中文](https://github.com/apache/casbin-gateway/blob/master/README_zh.md)\n\n**Every coding agent on the machine**\n\n**Any model vendor behind one endpoint**\n\nOne command. No database, no Go, no Node, no configuration.\n\nOn Linux and macOS:\n\n```\ncurl -fsSL https://raw.githubusercontent.com/apache/casbin-gateway/master/scripts/install.sh | bash\n```\n\nOn Windows, in PowerShell:\n\n```\nirm https://raw.githubusercontent.com/apache/casbin-gateway/master/scripts/install.ps1 | iex\n```\n\nEither 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, starts it, and arranges for it to start again when you log in. The terminal you installed from is yours again straight away.\n\nGateway then opens in its own window — no sign-in: it serves this machine only and signs the local admin in on sight. Closing that window leaves Gateway running behind its tray icon, which is also where you reopen the window, turn **Start at Login** off and on — **Settings → Startup** is the same switch — and quit for real. There is a **Casbin Gateway** entry on your desktop and in the Start menu, in `~/Applications`, or in the application menu, depending on the platform. An archive unpacked by hand gets the same entry the first time the launcher runs.\n\nIf you would rather use a browser, or you are on a machine with no desktop at all, everything is still at **[http://localhost:17000](http://localhost:17000)**, and `casbin-gateway start` runs the server on its own with no window and no tray.\n\nThat is the whole installation. Gateway keeps its data in a SQLite file inside its own directory.\n\nThe password behind that account is `admin` / `123`, and it only matters if you open Gateway to the network — see [Serving other machines](#serving-other-machines).\n\n| Every agent on this machine | Everything those agents carry | \n|---|---|\n| What each one runs on, which account it is signed in to, what it has spent there, and whether it is running | Every skill, MCP server and instruction file of every agent, side by side, copied from one agent to another | \n\n| What each agent is allowed to do | Which build each agent is on | \n|---|---|\n| Around forty switches over the agent's tools, models and providers, compiled to a Casbin policy and enforced on every request it relays | The build on this machine against the one its package manager publishes, installed, upgraded, rolled back or removed from the row it is on, whichever way it was installed | \n\n| What the agents spent | One endpoint per model vendor | \n|---|---|\n| Read from the transcripts the agents write themselves, so it counts what never went through Gateway too | 44 vendor presets, or any OpenAI- or Anthropic-compatible base URL | \n\n| Everything the agents relayed | The whole request, not just a count | \n|---|---|\n| Requests, tokens, cache hit rate and cost, broken down by model | The system prompt, every message, and the schema of every tool the model was offered | \n\n| Agent | Monitoring | Provider | MCP | Skills | Prompt | Sessions | Install | \n|---|---|---|---|---|---|---|---|\n|  **Claude Code** | ✅ | ✅ Anthropic | ✅ | ✅ | ✅ | ✅ | npm · brew · winget · script | \n|  **Claude Desktop** | ✅ | — | ✅ | — | — | ✅ | winget | \n|  **Codex CLI** | ✅ | ✅ OpenAI | ✅ | ✅ | ✅ | ✅ | npm · brew · winget · self | \n|  **ChatGPT Desktop (Codex)** | ✅ | ✅ OpenAI | ✅ | ✅ | ✅ | ✅ | store | \n|  **Gemini CLI** | ✅ | ✅ Gemini | ✅ | ✅ | ✅ | ✅ | npm | \n|  **Cursor** | ✅ | — | ✅ | ✅ | — | ✅ | brew · winget | \n|  **Cursor Agent** | ✅ | — | ✅ | ✅ | — | — | script · self | \n|  **Windsurf** | ✅ | — | ✅ | — | ✅ | — | brew · winget | \n|  **Cline** | — | ✅ OpenAI | ✅ | ✅ | — | — | npm | \n|  **Qwen Code** | ✅ | ✅ OpenAI | ✅ | ✅ | ✅ | ✅ | npm | \n|  **iFlow CLI** | — | ✅ OpenAI | ✅ | — | ✅ | — | npm | \n|  **Kimi Code CLI** | — | ✅ OpenAI | ✅ | ✅ | ✅ | — | npm · winget | \n|  **CodeBuddy Code** | — | ✅ OpenAI | ✅ | ✅ | ✅ | — | npm | \n|  **Roo Code** | — | — | — | — | — | — | — | \n|  **Copilot CLI** | — | — | — | — | — | — | npm | \n|  **Continue** | — | ✅ OpenAI | — | — | — | — | npm | \n|  **Zed** | — | ✅ OpenAI | — | — | — | — | brew · winget | \n|  **Aider** | — | ✅ OpenAI | — | — | — | — | — | \n|  **goose** | — | ✅ OpenAI | — | — | — | — | brew | \n|  **Crush** | — | — | — | — | — | — | npm · winget | \n|  **Droid** | — | ✅ OpenAI | — | — | — | — | — | \n|  **Trae** | — | — | — | — | — | — | brew · winget | \n|  **opencode** | ✅ | ✅ OpenAI | ✅ | ✅ | ✅ | ✅ | npm · winget · self | \n|  **opencode Desktop** | ✅ | ✅ OpenAI | ✅ | ✅ | ✅ | ✅ | winget | \n|  **OpenAgent** | ✅ | — | — | — | — | — | — | \n|  **OpenClaw** | ✅ | ✅ OpenAI | ✅ | ✅ | ✅ | ✅ | npm | \n|  **Hermes Agent** | ✅ | ✅ OpenAI | — | — | — | — | self | \n|  **DeepSeek Harness** | ✅ | ✅ OpenAI | ✅ | ✅ | — | ✅ | npm | \n|  **Pi** | — | ✅ OpenAI | — | ✅ | ✅ | — | npm | \n\n- **Monitoring** — audit-only records of what the agent did: prompts, tool calls, permission prompts. Nothing an agent does waits on Gateway, and no answer changes because monitoring is on.\n- **Provider** — Gateway writes the agent's own configuration to point it at a bound provider, in the wire format that agent's client speaks. An agent without it still reaches Gateway through the environment variables the UI shows.\n- **MCP · Skills · Prompt** — read, compare and copy MCP servers, skills and the instruction file between agents.\n- **Sessions** — prompts and token usage read straight from the agent's own transcripts, including what never went through Gateway.\n- **Install** — what Gateway installs, upgrades and removes it with: a package manager (npm, brew, winget, the Microsoft**store** ), the agent's own updater (**self** ), or the vendor's own install command (**script** ). An app that arrived as a setup program is still removed with the uninstaller it registered. Everything else is a download from its vendor's page.\n\n- **[Is the API behind that key what it was sold as?](#the-killer-feature-is-the-api-behind-that-key-what-it-was-sold-as)** — a reseller can quietly swap in a cheaper model or fake a cache hit, and none of it shows up in the traffic. Authenticity asks the upstream directly and grades it A–F.\n- **[Switch every agent's provider from one place](#send-an-agents-traffic-through-gateway)** — change an API key or base URL once, and every agent pointed at Gateway picks it up.\n- **[Add a provider, an MCP server, a prompt or a skill from a link](#import-from-a-link)** — click a vendor's \"add this\" button on the web and Gateway opens with what the link carries, before any of it is written.\n- **[Run several instances of one agent side by side](#what-to-do-next)** — e.g. multiple Claude Desktop instances, each signed in to a different account.\n- **[Install, upgrade and roll back the agents themselves](#what-to-do-next)** — which build each agent is on against what its package manager publishes, and back to an older release when an update broke something. One click whichever way it was installed, with the command shown first, its progress on the row and its console output a click away.\n- **[See the whole request, not just a count](#recording-prompts)** — every prompt, message and tool schema an agent sent, kept on this machine.\n- **[Say what each agent may do](#what-each-agent-is-allowed-to-do)** — around forty switches per agent, in groups, over its tools, models and providers, enforced by Casbin on every request it relays.\n- **[Know what every agent spent, even off Gateway](#what-the-agents-spend-including-what-never-went-through-gateway)** — read straight from the agents' own transcripts.\n- **[Compare and copy skills, MCP servers and prompts across agents](#what-to-do-next)** — every agent's install list in one table.\n- **[Connect an application once, for every agent](https://github.com/apache/casbin-gateway/blob/master/docs/user-manual/en/2-agents/2.5-connections.md)** — 45 of them: GitHub, Slack, Lark, DingTalk, Notion, Figma, Sentry, Kubernetes, Stripe, mail, a browser and the rest. The credential stays in Gateway rather than in each agent's config file, every call through it is checked against that agent's permissions, and testing one gives you a switch per tool.\n\nThe version Gateway is running sits in the top-right corner, with the date it was built, and it says **New** when the published build is a later one. Open it and press **Update now**: Gateway downloads the build for this machine, checks that it runs, puts it in place of itself and restarts into it. The page reloads on the new version when it comes back, and nothing else has to be touched — the data, the settings and the `casbin-gateway` command all stay where they are.\n\nGateway installed some other way, or in a directory it cannot write to, says so and shows the install command to run by hand instead.\n\n| Page | What you get | What it needs | \n|---|---|---|\n| **Agents** | Every AI coding agent installed on this machine — Claude Code, Codex CLI, Cursor, the Gemini CLI, opencode and more — four cards to a row, each naming the account it is signed in to, the provider it answers to, what it has spent — day by day over the last month, wherever its own transcripts carry that much — and whether it is running right now. Start or stop one from its card, or run several **instances** of the same agent at once, each with a state directory and an account of its own. An agent this machine does not have is listed too, and installed or upgraded from the page through the package manager the host already has. Gateway reports which installations have a newer release waiting, moves one onto any version its manager still publishes - back to an older one when an update broke something - and uninstalls it, leaving the agent's own sign-in and history where they are. An agent unpacked by hand is found on PATH as well as in the layouts installers write, and one hiding somewhere neither describes is pointed at directly:**Locate** browses this machine and lists the program you pick as an installation of its own, without moving or changing anything. | Nothing | \n| **Agent versions** | Every agent Gateway knows in one table: the build on this machine, the release its package manager publishes, and whether the two differ. Install one, upgrade it, put it back on an older release, or remove it, from the row it is on - in one click, whichever way it was installed: a package manager, the Microsoft Store, the uninstaller the app registered with Windows, the agent's own updater, or the vendor's own install command. The command is shown before it runs, the row carries its progress while it does, and everything it printed is a click away. | Nothing | \n| **Skills, MCP & Prompts** | Every skill, MCP server and instruction file of every agent in one table. Install skills from a GitHub repository, a `.zip` or`.tar.gz` , or a folder on this machine, into one agent or several at once. Add an MCP server the same way, edit the instructions an agent reads before every session, open one, delete it, or copy it into another agent. | Nothing | \n| **Sessions** | Every session those agents have had, read from the transcripts they leave on disk: the whole conversation, message by message. How many there are, how many ran today and how many came from a transcript rather than from monitoring, then filtered down to one agent or one of those two sources. | Nothing | \n| **Activity** | What a monitored agent is doing as it does it — each tool call, its target and how long it took. | Monitoring on for an agent | \n| **Providers** | One endpoint in front of your model vendors. Gateway holds the API key, so the agents never have it — or forwards the agent's own sign-in and holds nothing. | A vendor API key, or nothing at all | \n| **Authenticity** | A score out of 100 and a grade for every provider, measured without being asked — see [the section below](#the-killer-feature-is-the-api-behind-that-key-what-it-was-sold-as) . | A provider with an API key | \n| **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 provider, and `llmRecordMode` — see[Recording prompts](#recording-prompts) | \n| **Usage** | What every agent on this machine spent, over time and broken down by model and by agent, read from the agents' own transcripts — so it counts the requests that never went through Gateway. A second tab shows what Gateway relayed, which is the only account that knows which provider answered and whether it failed. | Nothing | \n| **Model pricing** | What a million tokens costs, which is what every figure on the Usage page is worked out from. Edit a price by hand, or let Gateway reprice the models this machine has run from the [models.dev](https://models.dev) catalogue on a schedule; a price you edited yourself is left alone. | Nothing | \n\nA page with tabs or sections is in the sidebar under its own name, so the rail lands on the test cases, the MCP servers or the security settings rather than only on the top of the page they are on. **⌘K**, or **Ctrl+K**, opens a search box over whichever page you are on: every page by name, and every agent and provider on this machine, which is how you reach one of thirty providers without paging through the list. The header switches the UI between light and dark, between three palettes — **Amber & Ink**, **Terminal** and **Indigo** — and between English and Chinese.\n\nAgents 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.\n\nA reseller can sell a frontier model and serve a cheaper one, count a cached prefix as fresh input, or answer in an API it only pretends to speak. None of that shows up in the traffic, so **Authenticity** asks the upstream directly. Every provider is probed on its own — when it is added, when its endpoint, type or key changes, when it has never been probed, and again whenever its report goes stale — and comes back with a score out of 100 and a grade from A to F, on the Authenticity page and above the agents on the home page. No button to press, and nothing to configure.\n\nThe score is only a summary of the test cases behind it, and every one of them is on the page. Half of them ask what the upstream is: whether the model that answers is the one that was asked for — worth half credit off a vendor's own endpoint, where that field is whatever the upstream typed there — which vendor the model says trained it, whether anything was injected in front of the request, whether several identical requests come back from the same model at all, and whether a parameter the API documents (`logprobs`, `n`, a stop sequence) is honoured, refused, or accepted with a 200 and quietly dropped. A test bank asks questions with one right answer, from counting the letters in a word to who wrote the Preface to the Pavilion of Prince Teng. The rest read the envelope: whether a two-level tool schema survives a forced call, whether the event stream carries everything the API documents, whether the prompt cache is really accounted for, whether two identical requests are billed the same, whether the vendor's own headers are there. Each case names the question it puts to the upstream, the exact request it sends, how the answer is judged, and what it is worth.\n\nReweight a case, turn it off, rewrite its question, or add one of your own — the questions worth asking of a reseller are not the same everywhere, and a score whose method is not published is not evidence. **Restore defaults** puts the shipped suite back and leaves your own cases alone.\n\nThe report has a second half that costs nothing and sends no request: what the records Gateway already kept say about that upstream — how much of the cache it really accounted for, how many attempts failed, how long it took to answer, and how much of what it served has no price.\n\nA probe spends a few cents of that provider's own credit, which is on the report next to the finding. `providerProbeIntervalHours` sets how often a report goes stale, `providerProbeMode = \"manual\"` probes only when asked, and `\"off\"` never probes.\n\nThis is what fills **LLM Records**, and what lets Gateway keep the vendor key instead of the agent.\n\n1. **Providers** →**Add** : pick the type (OpenAI- or Anthropic-compatible), paste the vendor base URL and API key, and list the models it serves.\n2. **Agents** → open an agent → pick that provider. For an agent whose configuration format Gateway knows,**Write configuration** puts it in the agent's own file —**Preview** shows exactly what that will be first, and**Restore** undoes it. Picking a different provider afterwards rewrites the file on the spot, so switching from then on is one click, from either page.\n3. For any other agent, copy the environment snippet the page shows and start the agent from a shell that has it:\n\n```\nexport ANTHROPIC_BASE_URL=\"http://localhost:17000/v1/agents/claude-code\"\nexport ANTHROPIC_AUTH_TOKEN=\"cg-...\"\n```\n\nThe token is Gateway's own relay token, not a vendor key: the agent refuses to start without something in that variable, and Gateway authenticates upstream with the provider's key instead. The snippet on the page already has the real value filled in.\n\nOne base URL answers whichever API the agent speaks: `/chat/completions` for an OpenAI client, `/v1/messages` for an Anthropic one, `/responses` for Codex, which speaks nothing else since it dropped the chat completions wire format, and `/v1beta/models/<model>:generateContent` for the Gemini CLI, which speaks only Google's own API. The API the provider serves need not be the same one: Gateway translates between all four, in both directions and for streamed answers too, so Codex runs on DeepSeek, Kimi or Qwen, and Claude Code runs on any of them just as well. A provider serving the very API the request arrived in is relayed byte for byte, untouched, since there is nothing to translate. The one thing Gateway answers itself is the token count an Anthropic or Gemini client asks for before each turn, which it estimates when the bound provider has no endpoint to ask.\n\nUpstream, which API Gateway calls is the provider's own setting: **Upstream API** defaults to the one the provider type implies, and can be set to Responses for OpenAI itself or for a relay that accounts cached input on `/responses` more accurately than on `/chat/completions`. A Codex request then meets a Responses provider on both sides and passes through byte for byte, cache counters included.\n\nAn agent signed in with a ChatGPT or Claude subscription has no API key to paste. Set the provider's **Authentication** to **the caller's own login** and it needs none: the base URL points at the vendor, and every request is forwarded with the credentials the agent itself sent, so it keeps its own sign-in. Leave **Models** empty and the provider accepts any model name.\n\nThe environment snippet for such a provider sets the base URL and nothing else — a token there would replace the sign-in the agent already has. Gateway records and routes the traffic exactly as it does for a provider with a key; it just never sees one.\n\nCodex is the exception: its ChatGPT sign-in talks to an endpoint no provider stands in for, so a Codex CLI still needs a provider with an API key.\n\nVendors put an \"add this to my agent manager\" button on their own pages, which opens a `ccswitch://` link rather than a page. Gateway reads that format instead of inventing a second one nobody would be given a button for, and one link carries any of four things: a provider with its base URL, key and model list; an MCP server, as the JSON block a server is written in; a set of instructions for an agent to read before every session; or a repository to install skills from.\n\nGateway claims the scheme on Windows and Linux when it starts, so clicking such a button opens Gateway on the **Import** page with everything the link held laid out — the arguments an MCP server would be run with, the instructions in full, the repository a skill would come from. **Nothing is written until the button under it is pressed**: the link came from a website, so its values are read first. A provider goes on to the Providers form, where it is reviewed and probed against the upstream before it is stored; the other three are written through the same endpoints that add one by hand, into the agents you tick. A link names apps rather than the agents on this machine, and the ones Gateway does not manage are named back to you rather than quietly dropped.\n\nThe link is handed to Gateway in the body of an API call, not in the address of the page it opens, because a provider link carries an API key and an address is kept in browser history and sent on to wherever the page navigates next.\n\nThe scheme is taken from whatever held it only the first time, and given back when Gateway is removed; a registration that is already Gateway's own is rewritten on every start, since it records a path that an update or a move would otherwise leave pointing at a Gateway that is no longer there. On macOS nothing is claimed at all — a URL scheme there belongs to an application bundle and the link arrives as an Apple event, which this launcher has no loop to receive, so declaring it would take links away from whatever can open them. Paste the link into the box on the Import page instead, which works on every platform; the Providers page takes a provider link the same way.\n\n**Permissions** in the sidebar is the page for it: every agent on this machine down one side, what the one you picked may do beside it. The same card is on the agent's own page. Everything that agent relays through Gateway is held to what is set there, so it can be given less than it came with without editing its own configuration.\n\n- **Tools** — around forty switches, in six groups: the terminal, reading the project, changing the project, the internet, planning and delegation, and one switch per MCP server that agent has installed. The switch on a group's header sets the whole group at once; open the group and the answer gets as fine as you like, since running a command, reading a running command's output and stopping one are three switches. A tool whose switch is off is taken out of the request before it leaves this machine, so the model is never offered it and the agent never gets to call it. Every agent names its tools differently, and`Bash` ,`shell` and`run_shell_command` are all the same switch. Each group ends in a catch-all for the tools Gateway has never seen, which is what closes a group for good rather than for the tools that happened to be listed the day it was set.\n- **Models** — any model, only the ones you pick, or all but them. A name may end in`*` , so`claude-opus-*` covers a whole family.\n- **Providers** — which of the providers this agent's requests may be sent to.\n\nA request that asks for something switched off comes back as a `permission_error` in the API the agent speaks, so it reads as a refusal rather than as a broken gateway.\n\nUnderneath, the switches compile to a [Casbin](https://casbin.org) policy, and every relayed request is decided by an enforcer rather than by a hand-written check. **Advanced** shows the `model.conf` and `policy.csv` they compile to, and takes extra policy lines of your own:\n\n```\np, claude-code, model:claude-opus-*, use, deny\np, claude-code, model:*, use, allow\np, claude-code, tool:shell/run, use, deny\np, claude-code, tool:mcp/github, use, allow\np, claude-code, tool:mcp/*, use, deny\np, claude-code, tool:*, use, allow\n```\n\nThe first rule that matches decides, which is what lets one exception stand in front of the rule behind it: every MCP server taken away except the one that stays. The lines you write yourself are checked before the ones the switches wrote.\n\nThe rules apply to what goes through the proxy, so an agent bound directly to a provider — its own configuration pointing at the vendor rather than at Gateway — is not held to them. The page says so where that is the case.\n\nAn agent on its own subscription relays nothing through Gateway, and a request that goes straight to the vendor\nleaves no record here — but the agent writes a transcript of it on disk anyway. **Usage** reads those\ntranscripts, so the spend of every agent on this machine is on one page from the first start, with no provider\nconfigured and nothing routed: tokens, cache hit rate and cost, over time and broken down by model and by\nagent. The second tab, **What Gateway relayed**, is the narrower account of the traffic that did come through,\nand the only one that knows which provider answered and whether it failed.\n\nEvery figure there is worked out from **Model pricing**, which is a table of what a million tokens costs.\nVendors change their prices and resellers do not follow, so a price can be edited by hand, and Gateway can\nreprice the models this machine has run from the [models.dev](https://models.dev) catalogue on a schedule\n(`modelsDevSyncMode`, `modelsDevSyncIntervalHours`), leaving anything you edited yourself alone.\n\n- **Stop** :`casbin-gateway stop` .**Start again** :`casbin-gateway start` .**Check** :`casbin-gateway status` . All three work from any directory — the command is a wrapper that always starts Gateway in its install directory, where its data lives.\n- **Run in the foreground** instead, to watch it:`casbin-gateway` , stopped with`Ctrl-C` . In the background its console output goes to`logs/casbin-gateway.out` .\n- **Upgrade** : press the version in the top-right corner and then**Update now** , or run the install command again. Your database and settings are untouched either way.\n- **Remove** :`casbin-gateway uninstall` first. Deleting the directory is the part you can do yourself; what it cannot undo is everything Gateway wrote outside it — the monitoring hooks in every agent's own configuration, the provider each agent was pointed at, the login entry, the desktop entries and the`ccswitch://` scheme, all of which would otherwise be left naming a program that is no longer there. It puts all of that back, then names the two things to delete by hand:`~/.local/share/casbin-gateway` and`~/.local/bin/casbin-gateway` (on Windows,`%LOCALAPPDATA%\\casbin-gateway` and its PATH entry).\n\nSet `INSTALL_DIR` to install somewhere else, `NO_START=1` to install without starting, or `NO_AUTOSTART=1` to install without starting at login.\n\nGateway binds `127.0.0.1` by default, because two things are wide open to whoever can reach the port: the UI signs the local admin in without asking, and `/v1` relays with the API keys stored here. Both are exactly what you want from a local tool and neither should be offered to a network.\n\nTo serve other machines anyway, set `httpaddr = 0.0.0.0` in `conf/app.conf`, and then:\n\n1. **Change the admin password** from**My Account** . The auto sign-in stops at the first request that is not from this machine, so from then on the password is the only thing in the way.\n2. **Send the relay token** with every request to`/v1` . Gateway generates one on first start and shows it under**Settings → Security** ; the environment snippets on the Providers and Agents pages already carry it, and it is what Gateway writes into the configuration of an agent it switches. Requests from this machine never need it.\n\n**These are nightly builds**, rebuilt from `master` on every push and published as the [`nightly`](https://github.com/apache/casbin-gateway/releases/tag/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.\n\n**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 & Prompts** 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: **Providers**, **Authenticity** and **LLM Records**.\n\nSo 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 for other machines.\n\nNo image is published, so the compose file builds one from a checkout of this repository:\n\n```\ndocker compose up -d\n```\n\nPodman reads the same file:\n\n```\npodman compose up -d\n```\n\nEither way the UI is on [http://localhost:17000](http://localhost:17000), the SQLite database lives in a named volume that survives `down`, and `conf/app.conf` is mounted from the repository, so the settings it seeds can be edited before the first start without rebuilding the image.\n\nEverything is optional. Settings are changed on the **Settings** page of the web UI and stored in the database, so nothing has to be edited by hand and nothing has to be restarted. `conf/app.conf`, next to the executable, seeds them on the very first start and explains each one; the one-step install has no file beside it and seeds from the copy baked into the binary instead. Editing the file after that first start does nothing, except for the keys read before the database is open: `httpport`, `driverName`, `dataSourceName`, `dbName` and `redisEndpoint`. The settings people actually change:\n\n| Setting | Default | What it does | \n|---|---|---|\n| `httpport` | `17000` | Port of the web UI and the REST API | \n| `httpaddr` | `127.0.0.1` | Interface the web UI binds to — see [Serving other machines](#serving-other-machines) | \n| `driverName` /`dataSourceName` | `sqlite` /`./data/casbin-gateway.db` | Where data is stored | \n| `llmRecordMode` | `full` | How much of each relayed LLM request is kept — including the prompt, see [Recording prompts](#recording-prompts) | \n| `providerProbeMode` | `auto` | Whether providers are probed for authenticity on their own, only when asked ( `manual` ), or never (`off` ) | \n| `apiKeyEncryptionKey` | empty | Encrypts provider API keys at rest (AES-256-GCM) | \n| `casdoorEndpoint` | empty | Switches sign-in over to [Casdoor](https://casdoor.org) SSO | \n\nGateway prints what it is actually doing when it starts, so the result can be checked instead of the file:\n\n```\n+---------------------------------------------------------------------+\n| Casbin Gateway                                                      |\n+---------------------------------------------------------------------+\n| Management UI | http://localhost:17000 (this machine only)          |\n| Settings      | Settings page, seeded from conf/app.conf            |\n| Web UI files  | web/build                                           |\n| Database      | sqlite, file \"./data/casbin-gateway.db\" (connected) |\n| Sign-in       | built-in user table, Casdoor is not configured      |\n| Relay auth    | this machine only, no token needed                  |\n+---------------------------------------------------------------------+\n```\n\nA previous Gateway still holding this port is stopped first, so a restart never waits on it. A port held by anything else stays with that program: Gateway names the process holding it and stops, rather than taking the port or starting half-configured.\n\n**Every relayed request is recorded in full from the first one, prompt included.** That is what the LLM Records page is made of, and none of it leaves this machine — but a prompt carries whatever was pasted into it, so if that is not what you want, this is the setting to change before you route an agent through Gateway. The picker at the top of the LLM Records page switches between **Recording off**, **Record metadata** and **Record metadata and bodies**, and takes effect from the next request; the Settings page holds the same choice and the limits around it, seeded from:\n\n```\n; The default is \"full\": it stores the request body, which is what LLM Records\n; needs to show prompts, messages and tool schemas. \"metadata\" records only who\n; called which model with which outcome, and \"off\" keeps nothing at all.\nllmRecordMode = \"full\"\nllmRecordRetentionDays = 30\nllmRecordMaxRecords = 10000\nllmRecordMaxPayloadBytes = 1048576\n```\n\nBodies 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.\n\nThe cost next to each record uses list prices, which vendors change and resellers do not follow. Correct them on the **Model pricing** page, or point `llmPricingFile` at a JSON file of your own rates.\n\n[Casdoor](https://casdoor.org) is optional and takes over member management. Create an organization and an application for Gateway in a Casdoor instance, then fill in the five fields of **Settings → Sign-in**. Sign-in redirects to Casdoor as soon as `casdoorEndpoint` is set, which also enables [OAuth logins](https://casdoor.org/docs/provider/oauth/overview).\n\nGo 1.25+, and Node.js with Yarn.\n\nThe backend serves the compiled frontend out of `web/build`, so build it once first:\n\n```\ncd web && yarn install && yarn build\ngo run main.go\n```\n\nThen open [http://localhost:17000](http://localhost:17000), where you are signed in as the local admin, same as an installed Gateway. The SQLite database is created on first start; there is no database server to install.\n\n```\ncd web && yarn dev\n```\n\nThat serves the UI on [http://localhost:16002](http://localhost:16002) with hot reload and proxies API calls to the backend on port 17000, so both have to be running.\n\nXORM 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:\n\n```\ndriverName = mysql\ndataSourceName = root:123@tcp(localhost:3306)/\ndbName = casbin_gateway\n```\n\nGateway normally reads two things from disk: `conf/app.conf` and the compiled UI in `web/build`. The `embed` build tag bakes both into the executable, which is what the install scripts ship:\n\n```\ncd web && yarn install && yarn build\ngo build -tags embed -o casbin-gateway .\n```\n\nBuild the frontend first — everything under `web/build` goes into the binary, so `go build -tags embed` fails to compile while that directory is missing.\n\nFiles on disk always win over the embedded copies, so a single binary can still be configured and developed against without rebuilding it:\n\n| Embedded asset | Overridden by | \n|---|---|\n| `conf/app.conf` | `conf/app.conf` in the working directory, or next to the executable | \n| `web/build` | `web/build/index.html` in the working directory, which then serves the whole UI | \n\nThe startup summary reports which source each one came from.\n\nBeing self-contained is about startup, not about staying read-only. A running Gateway writes `./data` (the SQLite database and 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.\n\nCasbin Gateway contains 2 parts:\n\n| Name | Description | Language | Source code | \n|---|---|---|---|\n| Frontend | Web frontend UI for Casbin Gateway | TypeScript + React + shadcn/ui | [https://github.com/apache/casbin-gateway/tree/master/web](https://github.com/apache/casbin-gateway/tree/master/web) | \n| Backend | RESTful API backend for Casbin Gateway | Golang + Beego + XORM | [https://github.com/apache/casbin-gateway](https://github.com/apache/casbin-gateway) | \n\n[User Manual](https://github.com/apache/casbin-gateway/blob/master/docs/user-manual/en/README.md) — every page of the UI, from installing Gateway to routing an agent through it.\n\nIf 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.", "url": "https://wpnews.pro/news/casbin-gateway-a-security-gateway-for-the-ai-coding-agents-on-your-machine", "canonical_source": "https://github.com/apache/casbin-gateway", "published_at": "2026-09-20 20:01:25+00:00", "updated_at": "2026-09-20 20:22:59.388406+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety", "ai-policy", "developer-tools"], "entities": ["Apache", "Casbin Gateway", "Claude Code", "Codex CLI", "Gemini CLI", "Cursor", "Windsurf", "Cline"], "alternates": {"html": "https://wpnews.pro/news/casbin-gateway-a-security-gateway-for-the-ai-coding-agents-on-your-machine", "markdown": "https://wpnews.pro/news/casbin-gateway-a-security-gateway-for-the-ai-coding-agents-on-your-machine.md", "text": "https://wpnews.pro/news/casbin-gateway-a-security-gateway-for-the-ai-coding-agents-on-your-machine.txt", "jsonld": "https://wpnews.pro/news/casbin-gateway-a-security-gateway-for-the-ai-coding-agents-on-your-machine.jsonld"}}