# Agentgate

> Collateralized execution engine for AI agents: bond-and-slash accountability with Ed25519 identities, bounded exposure, and progressive trust tiers.

- **Type:** MCP server
- **Install:** `agentstack add mcp-selfradiance-agentgate`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [selfradiance](https://agentstack.voostack.com/s/selfradiance)
- **Installs:** 0
- **Category:** [Databases](https://agentstack.voostack.com/c/databases)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [selfradiance](https://github.com/selfradiance)
- **Source:** https://github.com/selfradiance/agentgate
- **Website:** https://github.com/selfradiance/agentgate-governed-writefile-demo

## Install

```sh
agentstack add mcp-selfradiance-agentgate
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# AgentGate

AgentGate is a collateralized execution engine for AI agents: Ed25519 identities, reusable bonds, bounded exposure, settlement and slashing, and progressive trust tiers at the point where agents attempt external actions.

[](https://github.com/selfradiance/agentgate/actions/workflows/ci.yml)

> [!IMPORTANT]
> **Where To Start**
>
> AgentGate is the deeper accountability substrate underneath parts of this ecosystem. It is usually not the first repo a cold reader should start with.
>
> Recommended order for a first read:
> 1. [Governed WriteFile Demo](https://github.com/selfradiance/agentgate-governed-writefile-demo) for the narrowest end-to-end proof path
> 2. [MCP Firewall](https://github.com/selfradiance/agentgate-mcp-firewall) for the governed tool-call layer built on top
> 3. AgentGate (this repo) for the underlying execution engine
>
> Start here when you want the engine itself: identity registration, bond lifecycle, bounded exposure accounting, settlement and slashing, and transport/security mechanics.

AgentGate is the bond-and-slash substrate itself. It requires signed identities, lets agents lock reusable bond capacity against declared exposure, and records settlement outcomes in durable audit state at the deterministic choke point between agents and external actions.

## Visual Explainer

Prefer a short visual overview before reading? Watch the 4-part intro on X:

**[AgentGate explainer thread (4 short videos)](https://x.com/selfradiance11/status/2046010251128832398)**

Covers:
- what AgentGate is
- why permissions are not enough
- a concrete MCP Firewall example
- why runtime accountability matters

## What AgentGate Does

AgentGate sits between an autonomous agent and an external action such as an API call, bid, or financial operation. Before the action runs, the agent authenticates with an Ed25519 identity and locks bond capacity against a declared exposure.

If the action resolves cleanly, the reserved exposure is settled and released. Manual `malicious` resolution now requires two distinct eligible resolver identities before the slash finalizes, while expired-action sweeps still slash immediately. The system keeps that lifecycle in durable audit state and uses prior outcomes to gate larger bond sizes through progressive trust tiers.

This is a narrow runtime accountability mechanism, not a general AI safety or general agent security solution.

The `actions` table serves double duty: it's both a real-time enforcement log for slashing and a durable post-incident audit trail that can support disclosure to affected parties. The threat model doc covers this in more detail.

> **[Threat Model →](docs/threat-model.md)** — What AgentGate defends against, what it doesn't, and why.

Read the full story: **[How I Built AgentGate](docs/manifesto.md)**

---

## Quick Integration

AgentGate works with any agent that can make HTTP requests. If you want the shortest outsider-readable proof, start with the [Governed WriteFile Demo](https://github.com/selfradiance/agentgate-governed-writefile-demo). If you want the governance layer that fronts MCP tools, see [MCP Firewall](https://github.com/selfradiance/agentgate-mcp-firewall). The flow below is the direct engine integration path: register an identity, lock a bond, execute an action against that bond, and resolve the outcome.

> **Note:** The curl examples below assume `AGENTGATE_DEV_MODE=true` for local development (auth key enforcement is skipped). In production, add `-H 'x-agentgate-key: YOUR_KEY'` to every POST request.

**1. Register an identity**

Identity registration requires proof-of-possession — the caller must sign the request with the private key matching the public key being registered. This uses the same signature headers as all other state-changing endpoints:

```bash
curl -s http://127.0.0.1:3000/v1/identities \
  -H 'content-type: application/json' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "publicKey": "" }'
```

Returns an `identityId` (e.g., `id_abc123`).

**2. Lock a bond**

All state-changing requests must be signed. Headers required on every request:

- `x-agentgate-timestamp` — current time in epoch milliseconds
- `x-agentgate-signature` — Ed25519 signature over `sha256(nonce + method + path + timestamp + JSON.stringify(body))`
- `x-nonce` — a unique string per request (UUID recommended); bound into the signed message AND stored server-side — the server rejects duplicates per identity, providing replay protection on top of the timestamp window

```bash
curl -s http://127.0.0.1:3000/v1/bonds/lock \
  -H 'content-type: application/json' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "identityId": "id_abc123", "amountCents": 5000, "currency": "USD", "ttlSeconds": 300, "reason": "marketplace bid" }'
```

Returns a `bondId`.

**3. Execute a bonded action**
```bash
curl -s http://127.0.0.1:3000/v1/actions/execute \
  -H 'content-type: application/json' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "identityId": "id_abc123", "bondId": "bond_xyz", "actionType": "place-bid", "payload": { "item": "widget-42", "price": 1500 }, "exposure_cents": 1500 }'
```

Returns an `actionId`. The bond's available capacity is reduced by `ceil(exposure_cents × 1.2)`.

**4. Resolve the action**
```bash
curl -s http://127.0.0.1:3000/v1/actions//resolve \
  -H 'content-type: application/json' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "outcome": "success", "resolverId": "id_resolver123" }'
```

Outcome must be one of: `success`, `failed`, or `malicious`. On `success` or `failed`, that action's reserved exposure is settled and released immediately. On manual `malicious`, the first eligible resolver vote returns a pending response with `finalized: false`, `actionStatus: "open"`, `maliciousVotes: 1`, and `maliciousVotesRequired: 2`; the second distinct eligible resolver vote finalizes the slash. Sweeper auto-slash on expired actions remains immediate.

### Common Errors

| Error | Cause | Fix |
|---|---|---|
| `INVALID_SIGNATURE` | Missing signature headers, stale timestamp, or signature doesn't match the signed payload | Verify you're signing `sha256(nonce + method + path + timestamp + JSON.stringify(body))` with the correct private key and a fresh timestamp |
| `MISSING_NONCE` | `x-nonce` header is missing | Send a fresh nonce on every POST request |
| `DUPLICATE_NONCE` | Same nonce reused by the same identity | Generate a fresh UUID for every request |
| `DUPLICATE_MALICIOUS_VOTE` | Same resolver tried to cast a second malicious vote for the same open action | Use a different eligible resolver identity for the second malicious vote |
| `TIER_BOND_CAP_EXCEEDED` | Bond amount exceeds identity's trust tier cap | Build reputation with successful resolutions to unlock higher tiers |
| `INSUFFICIENT_BOND_CAPACITY` | Bond doesn't have enough remaining capacity | Lock a larger bond or resolve outstanding actions to free capacity |
| `RATE_LIMIT_EXCEEDED` | More than 10 executes in 60 seconds for this identity | Wait and retry, or spread actions across a longer window |
| `IDENTITY_BANNED` | Identity has been banned (manually or after 3 malicious resolutions) | Contact the operator or use a different identity |
| `SERVER_MISCONFIGURED` | Auth key not set and `AGENTGATE_DEV_MODE` is not `true` | Set the missing auth key or set `AGENTGATE_DEV_MODE=true` for local dev |

For the full security posture, see the **[Threat Model](docs/threat-model.md)**.

---

## Core Concepts

### Identity

- Ed25519 public key (raw 32-byte base64)
- All state-changing endpoints require signed requests — including identity registration itself, which requires proof-of-possession (the caller must sign the request with the private key matching the public key being registered)
- Public key uniqueness enforced at the database level — duplicate identity registration is rejected with `409 DUPLICATE_IDENTITY`
- Replay protection via timestamp validation (60-second window, 5-second future tolerance) AND nonce store (duplicate rejection per identity)
- Named agent support: set `AGENTGATE_AGENT_NAME` env var to create separate identity files per agent (e.g., `agent-identity-trader.json`)

Signed message format: `sha256(nonce + method + path + timestamp + JSON.stringify(body))`

Required headers: `x-agentgate-timestamp`, `x-agentgate-signature`, `x-nonce`

### Reusable Bond Model

Bonds are not single-use. Each bond represents reusable execution capacity.

- **Capacity rule:** effective exposure = `ceil(declared_exposure × 1.2)`
- **Constraint:** `outstanding_exposure_cents + effective_exposure /resolve \
  -H 'content-type: application/json' \
  -H 'x-agentgate-key: YOUR_KEY' \
  -H "x-agentgate-timestamp: $TIMESTAMP" \
  -H "x-agentgate-signature: $SIGNATURE" \
  -H "x-nonce: $(uuidgen)" \
  -d '{ "outcome": "yes", "resolverId": "id_abc123" }'
```

**MCP tools:** `create_market` and `resolve_market` expose the same flow to Claude Desktop.

The dashboard shows a live Markets table with status color-coding (open → amber, resolved → green).

---

## Dashboard

AgentGate includes a real-time HTML dashboard at http://127.0.0.1:3000/dashboard. It shows:

- Summary bar with identity, bond, action, and market counts
- Per-identity reputation scores with color coding and trust tier labels (Tier 1 New / Tier 2 Established / Tier 3 Trusted)
- Tables for bonds, actions, identities, and markets with truncated IDs and status indicators
- Agent names displayed per identity (for multi-agent setups)
- `[BANNED]` tags on banned identities

The page auto-refreshes every 5 seconds. The server must be running.

---

## Health Check

```
GET /health
```

Returns `200 OK` with `{ "status": "ok", "timestamp": "" }`. No authentication required — designed for external uptime monitors (e.g., UptimeRobot).

---

## Running Locally

**Install:**

```
npm install
```

**Start server:**

```
npm run restart
```

This kills any old server process on port 3000 and starts fresh. Fastify REST API runs at http://127.0.0.1:3000, MCP HTTP server at http://127.0.0.1:3001/mcp, dashboard at http://127.0.0.1:3000/dashboard. The sweeper, nonce cleanup, and bucket cleanup logs appear every 60 seconds. Database file is created automatically at `data/agentgate.sqlite` on first run, with automatic backups to `data/backups/` on each startup (keeps the 5 most recent).

**Run tests:**

```
npm run test
```

123 tests across 12 test suites (API, MCP integration, prediction markets, sweeper, red team, outbound HTTP, dashboard, adapter).

---

## Security

### Nonce Replay Protection

All POST endpoints require an `x-nonce` header. The server stores each nonce per identity and rejects duplicates with a `409 DUPLICATE_NONCE` response. The `AgentAdapter` generates UUID nonces automatically. Expired nonces (older than 5 minutes) are cleaned up every 60 seconds alongside the sweeper. This provides replay protection on top of the 60-second timestamp window.

### MCP Endpoint Authentication

The MCP HTTP endpoint (port 3001) is protected by a shared-secret header.

- Set `AGENTGATE_MCP_KEY` in your `.env` file (loaded automatically via dotenv on startup)
- If the key is **not set** and `AGENTGATE_DEV_MODE` is not `true`, requests are rejected with `500 SERVER_MISCONFIGURED`
- If the key is **not set** and `AGENTGATE_DEV_MODE=true`, auth is skipped (suitable for local dev)
- If the key **is set**, any request to `/mcp` without a matching `x-agentgate-key` header receives a `401 UNAUTHORIZED` response

`.env` entry:

```
AGENTGATE_MCP_KEY=your-long-random-secret
```

### REST API, Admin & Dashboard Authentication

Auth is split into three independent environment variables, each protecting a different surface:

| Variable | Protects | How it's checked |
|---|---|---|
| `AGENTGATE_REST_KEY` | All non-admin POST routes (bonds, actions, markets) | `x-agentgate-key` header |
| `AGENTGATE_ADMIN_KEY` | Admin endpoints (`/admin/ban-identity`, `/admin/unban-identity`) | `x-agentgate-key` header |
| `AGENTGATE_DASHBOARD_KEY` | Dashboard (`/dashboard`) | HTTP Basic Auth (username `admin`, password = key value) |

- **Auth is required by default.** If a key is **not set** and `AGENTGATE_DEV_MODE` is not `true`, requests to that surface are rejected with `500 SERVER_MISCONFIGURED`
- If `AGENTGATE_DEV_MODE=true`, missing keys are allowed and auth is skipped for that surface (suitable for local dev)
- If a key **is set**, requests without a valid credential receive `401 UNAUTHORIZED`
- Each key can be rotated independently without affecting the others

```
AGENTGATE_DEV_MODE=true              # skip auth enforcement for local dev (default: not set — auth required)
AGENTGATE_REST_KEY=your-rest-secret
AGENTGATE_ADMIN_KEY=your-admin-secret
AGENTGATE_DASHBOARD_KEY=your-dashboard-secret
```

### Identity Governance

Operators can ban and unban identities via the admin API. Banned identities receive `403 IDENTITY_BANNED` on all `lockBond` and `executeAction` calls.

```bash
# Ban an identity
curl -s http://127.0.0.1:3000/admin/ban-identity \
  -H 'content-type: application/json' \
  -H 'x-agentgate-key: YOUR_KEY' \
  -d '{ "publicKey": "" }'

# Unban an identity
curl -s http://127.0.0.1:3000/admin/unban-identity \
  -H 'content-type: application/json' \
  -H 'x-agentgate-key: YOUR_KEY' \
  -d '{ "publicKey": "" }'
```

**Auto-ban:** an identity is automatically banned after 3 malicious action resolutions. The trigger logs an `identity_auto_banned` security event.

### Security Event Logging

All security-relevant events are logged as structured JSON to stderr with an `event` field for easy filtering:

| `event` | Trigger |
|---|---|
| `auth_failed` | Wrong or missing `x-agentgate-key` on REST or MCP |
| `signature_failed` | Missing headers, stale timestamp, or bad Ed25519 signature |
| `duplicate_nonce` | Same nonce reused by the same identity |
| `bond_slashed` | Action resolved as malicious (via API or sweeper) |
| `identity_auto_banned` | Identity automatically banned after 3 malicious resolutions |
| `outbound_blocked` | `market.http` action blocked by allowlist or protocol check |

Each entry includes relevant context: `identityId` (truncated), `endpoint`, `reason`, and `requestId` where available.

---

## Security Hardening

AgentGate v0.2.0 was put through a structured red team process before release: 20 adversarial attack scenarios written as automated tests across 5 phases.

**Invariant validator** — every attack test ends by calling `validateInvariants(db)`, which runs 8 SQL assertions against the live database: bond amounts never go negative, outstanding exposure never exceeds bond capacity, settlement is always consistent, and nonces are never duplicated. Any state corruption — however subtle — causes an immediate test failure with a precise diagnostic.

**Attack phases:**

| Phase | Focus | Tests | Findings |
|---|---|---|---|
| 1 | Bond/exposure math | 6 | Fixed: `slashed_cents` not written to DB on malicious resolution; negative `exposure_cents` bypassed service-layer guard |
| 2 | Sweeper edge cases | 3 | Confirmed: resolve/sweep race is safe (SQLite serialization); double-slash prevented by open-action query |
| 3 | Replay attacks | 4 | Confirmed: nonce check catches replays even within the 60-second timestamp window; parallel duplicate nonce via `Promise.all` safely rejected |
| 4 | SQLite concurrency | 2 | Confirmed: `better-sqlite3` synchronous transactions serialize concurrent requests correctly |
| 5 | Outbound HTTP | 9 | Fixed: redirect bypass SSRF (allowlisted host could 302 to non-allowlisted target); IPv6 bracket allowlist bug (`[::1]` vs `::1`) |

**Total: 3 logic bugs fixed, 1 SSRF vulnerability fixed, 29 red team

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [selfradiance](https://github.com/selfradiance)
- **Source:** [selfradiance/agentgate](https://github.com/selfradiance/agentgate)
- **License:** MIT
- **Homepage:** https://github.com/selfradiance/agentgate-governed-writefile-demo

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-selfradiance-agentgate
- Seller: https://agentstack.voostack.com/s/selfradiance
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
