Tollgate
# Tollgate — the governance gate for AI agents that move money




> Deterministic policy enforcement, human approvals with cryptographic
> non-repudiation, budget escrow with clawback, and a tamper-evident audit
> chain — a **zero-dependency** governance gateway for any agent that touches
> money. Exposed to any agent as an **MCP server**.
[](https://pypi.org/project/tollgate-gateway/)
[](https://pypi.org/project/tollgate-gateway/)
[](https://github.com/kkU1444/tollgate/actions/workflows/ci.yml)
[](#install)
[](LICENSE)
[](#mcp-server)
[中文文档](README.zh-CN.md)
**Axiom:** the LLM is an untrusted component. It proposes; deterministic code
disposes. Every tool call an agent makes passes through the gate — and the
gate never guesses.
## Why
Teams won't give agents spending authority because three questions have no
answer: *what exactly can it do? who is accountable? can we audit it
afterwards?* Tollgate answers all three with a single enforcement point that
every tool call must cross:
```
unknown tool → rate limit → circuit breaker → integrity check → read cache
→ accountability → blacklist → budget reserve → field-semantics contract
→ injection fence → financial guardrails → adaptive risk score
→ policy rules (with business-calendar context) → authenticated approval
→ execution → hash-chained audit
```
Denial reasons flow back to the model so it can adapt. Anything that isn't
explicitly allowed is denied — and that default is *measured*, not assumed
(see [Red team](#red-team)).
## 60-second demo
```bash
git clone https://github.com/kkU1444/agent-governance-gateway.git && cd agent-governance-gateway
python -m tollgate demo-web # no API key needed
# open http://127.0.0.1:8765 — approve the $4,200 transfer in the browser
```
A travel agent runs a 15-step adversarial scenario: read-only calls pass,
external email is blocked, a replayed payment is caught, a negative amount is
rejected, an unregistered agent is refused, an unknown payee forces human
sign-off, and an over-limit transfer hits a hard cap. Then the ops team
freezes and claws back a fraudulent transfer, and the payee lands on a
blocklist.
## The money lifecycle
Approving a transfer is not the same as money leaving the building:
```
reserve (atomic, concurrency-safe) → human approval → execution
→ holding period (24h, funds in escrow, reversible)
→ settle | freeze → recover (clawback) → payee blacklist (permanent escalation)
```
Budget pools use `BEGIN IMMEDIATE` critical sections so concurrent agents see
"occupied", not a stale balance. Clawbacks are two-step (freeze, then
recover), each with operator identity and reason written into the audit
chain.
## Security architecture
| Threat | Mechanism |
|---|---|
| Prompt injection | deterministic fence + pattern guard + **canary tokens** planted in context (a canary in tool args is *proof* of hijack → hard deny) |
| Semantic misreading (`risk_score: 0.87` read as "high confidence") | **field-semantics contract** — meaning and thresholds of risk fields are owned by code; upstream risk signals auto-flag payees |
| Model hallucination / loops | forced replanning protocol, loop breaker, goal-anchored compaction |
| Concurrency / stale state | atomic budget reservation (BEGIN IMMEDIATE) |
| Rogue or unattributed agents | accountability registry — no registered owner, no execution; per-agent caps; approver authority levels |
| Forged approvals | HMAC or **Ed25519** signatures (pure-Python RFC 8032); private keys stay with approvers; `verify-approval` recomputes; four-eyes for large amounts |
| Tool supply-chain tampering | registration/runtime tool fingerprints vs sealed baseline |
| Credential theft | credential broker issues per-call, short-lived scoped credentials; secret scanning masks leaks in args/results |
| Audit tampering | SQLite hash chain + `verify` locates the exact tampered row; policy hot-reloads are themselves chain-anchored |
| Resource exhaustion (DoS) | per-agent sliding-window rate limits (money is capped by budget breakers, compute by rate limits) |
Policy files hot-reload on change; every reload lands in the chain as a
`policy.reload` entry (old hash → new hash), so *changes to the law itself*
are auditable.
**Learning loop:** attacks are auto-recorded into an intel base, replayed on
every change (`tollgate drill`), and live samples merge into the red-team corpus
(`tollgate redteam`) — real attacks become permanent regression tests.
## Red team
```bash
$ python -m tollgate redteam
恶意样本拦截: 32/32 (召回率 100%) injection corpus: 32/32 blocked
良性样本误报: 0/8 (误报率 0%) false positives: 0/8
策略不变量模糊测试: 300 例,四条不变量全部成立 300 fuzz cases, all invariants hold
```
Four fuzz invariants: the governance layer never crashes; execution is
fail-closed (actual executions ≡ audited executions); the hash chain survives
any input sequence; same seed reproduces exactly. [THREAT_MODEL.md](THREAT_MODEL.md)
maps mechanisms to STRIDE, OWASP Agentic AI threats and MITRE ATLAS, and keeps
an honest residual-risk list.
## MCP server
```bash
python -m tollgate mcp # stdio JSON-RPC 2.0, zero dependencies
```
Point any MCP client (Claude Desktop, Cursor, your own agent) at it — every
tool call crosses the full enforcement chain. Policy denials come back as
`isError` tool results with reasons, so the calling model can adapt.
### MCP upstream proxy — govern *any* MCP server
Tollgate is also an MCP **client**: point it at any third-party MCP server
and it spawns the upstream, pulls `tools/list`, and registers every tool
under a namespaced prefix (`upstream__tool`) — each call then crosses the
full enforcement chain, and upstream tools follow the same policy rules as
local ones:
```yaml
mcp_upstreams:
- name: github
command: ["npx", "-y", "@modelcontextprotocol/server-github"]
allow_tools: ["get_*", "list_*"]
risk: read
```
(A bundled demo upstream ships as `python -m tollgate.mcp_demo_upstream`;
see the `mcp_upstreams` section in `policies/demo.yaml`. A runnable real-world
example — a zero-dependency filesystem MCP server at `examples/fs_server.py`,
wired read-only allow / write deny — is configured there too: `fs_write_file`
is rejected fail-closed and never touches the disk.)
To use Tollgate as the governance hub for Claude Desktop (or any MCP
client), see **[docs/claude-desktop.md](docs/claude-desktop.md)** — one
config entry gives every tool call (yours *and* every upstream's) the
full enforcement chain, with the audit hash chain as the receipt.
## Install
Python ≥ 3.10, **zero mandatory dependencies** (a bundled YAML-subset parser
is used unless PyYAML is installed):
```bash
pip install tollgate-gateway # PyPI (import / CLI name stays `tollgate`)
pip install -e . # or from this repository
tollgate demo # or: python -m tollgate demo
```
## CLI surface
| Command | Purpose |
|---|---|
| `demo` / `demo-web` | scripted adversarial scenario (terminal / web approvals) |
| `serve` | approvals console + audit replay UI |
| `transfers`, `freeze`, `recover`, `release` | settlement ledger operations (authenticated) |
| `verify` / `verify-approval` | hash-chain check / approval signature check |
| `trace`, `reconcile` | attribution timeline / cross-ledger reconciliation |
| `explain`, `compliance` | decision rationale / compliance report |
| `budgets`, `usage` | budget pools / token & latency metering |
| `golden`, `redteam`, `drill` | drift regression / adversarial suite / attack replay |
| `agent` | live agent against any OpenAI-compatible endpoint |
| `onboard` | risk-grade a new tool, draft policy YAML |
| `seal`, `keygen` | tool fingerprint baseline / Ed25519 keypairs |
## Docs
- [威胁模型 / Threat model](THREAT_MODEL.md) — trust boundaries, STRIDE mapping, residual risks
- [中文文档](README.zh-CN.md)
## Roadmap
- Ed25519 for terminal approvals & settlement ops (web approvals done)
- Pluggable storage (Postgres) for multi-instance deployments
- Delegation chains (agent acting on behalf of a department)
- Policy-change approval workflow (policy as a first-class audited asset)
## Contributing & security
See [CONTRIBUTING.md](CONTRIBUTING.md). Security issues: follow
[SECURITY.md](SECURITY.md) — please do not open public issues for
vulnerabilities.
## License
[Apache-2.0](LICENSE)
TDQS
Scored across 13 tools
Each tool mostly maps to a distinct resource/action, and the demo/fs namespaces clarify provenance. erp_query_invoice vs parse_invoice_text and risk_check vs transfer_money are related but separable; only the generic demo_gated_action could conceptually overlap with governed writes like send_email or transfer_money.
Most names are snake_case, but prefix conventions are mixed: demo__demo_*, fs__fs_*, erp_query_invoice without a namespace separator, and bare names like get_calendar and send_email. The verb_noun pattern is common but not uniform, with page_oncall and risk_check deviating.
Thirteen tools fit comfortably in a well-scoped range and cover demo governance, filesystem access, ERP invoice lookup, calendar/weather/flights, messaging, payments, and risk checks. No tool appears purely redundant, although the demo tools are scaffolding.
The surface spans many domains but most have only one operation: filesystem has read/list but no write, calendar only get, email only send, and flights only search. This may be acceptable for an illustrative governance gateway, but agents would encounter dead ends for update/delete/read workflows.