Skip to content

selfradiance/agentgate

Repository files navigation

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.

CI

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 for the narrowest end-to-end proof path
  2. 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)

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 → — What AgentGate defends against, what it doesn't, and why.

Read the full story: How I Built AgentGate


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. If you want the governance layer that fronts MCP tools, see 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:

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": "<base64-encoded Ed25519 public key>" }'

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
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

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

curl -s http://127.0.0.1:3000/v1/actions/<actionId>/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.


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 <= amount_cents
  • If exceeded → INSUFFICIENT_BOND_CAPACITY
  • TTL cap: maximum 86400 seconds (24 hours) — requests exceeding the cap are rejected
  • Bond status lifecycle during normal settlement: activeoccupied (when action attached) → released / burned / slashed
  • Idle bonds that are touched after their TTL has elapsed are marked expired; expired bonds with open actions are handled by the sweeper

Exposure Lifecycle

Bonds support multiple concurrent actions. Each action reserves its own slice of the bond's capacity, and resolving one action only releases that action's exposure — other open actions on the same bond are unaffected.

  • Execute: exposure reserved, outstanding_exposure_cents incremented, bond marked occupied
  • Resolve (success): that action's exposure released; refund_cents accumulated on the bond; when the last open action settles and no prior burn/slash exists, the bond closes as released
  • Resolve (failed): that action's exposure released; 95% of that action's effective exposure goes to refund_cents, 5% to burned_cents; when the last open action settles and no slash exists, the bond closes as burned
  • Resolve (malicious): manual resolution uses a narrow dual-control seam: the first eligible non-executor resolver records a pending malicious vote and leaves the action open; the second distinct eligible resolver releases that action's exposure, reduces amount_cents (clamped at zero), increases slashed_cents, and closes the bond as slashed when no open actions remain
  • Settlement accounting: refund_cents, burned_cents, and slashed_cents accumulate as each action settles; closed_at is written when the last open action on the bond resolves

Auto-Slash Sweeper

A background sweeper runs every 60 seconds, checking for actions whose associated bond has expired while the action is still open. Any such action is automatically resolved as malicious and slashed immediately; the sweeper does not go through the manual dual-control vote path. On the same 60-second interval, the server also cleans up expired nonces (older than 5 minutes) and expired rate-limit buckets (older than 60 seconds). All three run with clean shutdown on SIGINT/SIGTERM.

Reputation Scoring & Trust Tiers

Each identity accumulates a reputation score based on its history:

score = locks×2 + actions×3 + successes×10 - failures×5 - malicious×20

The dashboard shows per-identity scores with color coding (green for positive, red for negative, gray for zero). Available via the get_reputation MCP tool or the dashboard.

Progressive Trust Tiers — bond capacity is reputation-gated. Tiers are purely computed from resolution history at bond-lock time (no stored state):

Tier Label Requirement Bond Cap
1 New Default 100¢
2 Established 5 qualifying successes from 2 distinct resolvers, 0 malicious 500¢
3 Trusted 20 qualifying successes from 20 distinct resolvers, 0 malicious No tier cap (normal capacity rules)
  • Any malicious resolution forces immediate demotion to Tier 1
  • Only successes with at least 100¢ effective exposure count toward promotion
  • Trust tiers are computed from the resolution history record only: outcome, effective exposure, and resolver identity
  • Banned identities are blocked separately; the dashboard keeps showing the history-based tier and adds a [BANNED] tag
  • Exceeding the tier cap returns 403 TIER_BOND_CAP_EXCEEDED

Recent hardening

  • What changed: manual malicious action resolution now requires two distinct eligible resolver identities; the first vote is stored as a pending malicious vote, the second vote finalizes the existing slash path, and success, failed, plus sweeper auto-slash stay unchanged.
  • How to verify: run npm test -- --run test/app.test.ts test/trust-tier.test.ts test/sweeper.test.ts, then npm test, npm run lint, and npm run build.
  • Next steps: if you ever need broader adjudication than this one manual malicious seam, add it as a separate design instead of expanding this into a general quorum system.

Outbound HTTP Safety (market.http)

  • Host:port allowlist — each allowlist entry is a host:port pair (e.g., localhost:3000). Wildcard port supported (e.g., localhost:*). Default allowlist is fail-closed to loopback ports 80 and 443 only; local dev services on custom ports must be explicitly listed in AGENTGATE_HTTP_ALLOWLIST
  • http/https only (default port inferred: 80 for http, 443 for https)
  • Timeout (default 2500ms)
  • Max request size, max response size
  • Response sanitization — before persisting outbound response data to the database, headers are stripped and the body is truncated to 1024 characters (with a [truncated] marker). This only affects storage — the full response is still returned to the caller
  • Redirect protectionfetch is called with redirect: "manual"; every redirect hop has its Location header re-checked against the allowlist before following (max 5 hops). Prevents an allowlisted host from acting as a trampoline to a non-allowlisted host.
  • Errors wrapped as DESTINATION_BLOCKED or REDIRECT_BLOCKED

Environment variables: AGENTGATE_HTTP_ALLOWLIST, AGENTGATE_HTTP_TIMEOUT_MS, AGENTGATE_HTTP_MAX_BODY_BYTES, AGENTGATE_MAX_RESPONSE_BYTES


MCP Transport

AgentGate exposes its 7 tools to Claude Desktop over two transports:

  • Streamable HTTP (recommended) — Express server on port 3001 at /mcp. Claude Desktop connects via mcp-remote. Sessions are managed server-side with a 100-session cap, 1MB body size limit, and automatic cleanup of sessions idle for more than 5 minutes.
  • stdio — launches src/mcp/server.ts as a subprocess directly from Claude Desktop.

Both transports require the AgentGate HTTP server (npm run restart) to be running.

Claude Desktop config (HTTP transport via mcp-remote)

Local:

{
  "mcpServers": {
    "agentgate": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://127.0.0.1:3001/mcp"]
    }
  }
}

File location: ~/Library/Application Support/Claude/claude_desktop_config.json

MCP Tools (7 total)

Tool Description
create_identity Create or load an Ed25519 agent identity
lock_bond Lock a bond (stake) for an identity
execute_bonded_action Execute a bonded action through the gate
resolve_action Resolve an action as success/failed/malicious
get_reputation Get identity reputation score
create_market Create a prediction market with a yes/no question and resolution deadline
resolve_market Resolve an open market as yes/no — settles all positions automatically

Prediction Markets

AgentGate includes a prediction market demo that illustrates multi-agent economic coordination using bonds as stake.

How it works:

  1. An operator creates a market with a yes/no question and a resolution deadline (must be a valid future ISO 8601 timestamp)
  2. Agents take positions by executing a market.position action against a locked bond, declaring a side of yes or no
  3. When the market resolves, winning positions are settled as success and losing positions as failed; each position settles only its own reserved exposure, so shared bonds stay occupied until every attached action is resolved

REST endpoints:

# Create a market
curl -s http://127.0.0.1:3000/markets \
  -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 '{ "creatorId": "id_abc123", "question": "Will BTC hit 100k by Friday?", "resolutionDeadline": "2026-04-20T00:00:00Z" }'

# Resolve a market
curl -s http://127.0.0.1:3000/markets/<marketId>/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": "<ISO>" }. 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.

# 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": "<base64-encoded Ed25519 public key>" }'

# 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": "<base64-encoded Ed25519 public key>" }'

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 tests passing.

Full attack scenarios documented in docs/red-team-plan.md.

Post-v0.2.0 hardening (Session 15): A cold-eyes security audit identified additional issues that have since been fixed:

  • Timestamp validation now rejects future-dated timestamps (>5 seconds ahead) in addition to stale ones, closing a clock-skew attack vector
  • Dashboard HTML output is now XSS-safe — all database-backed values are escaped via escapeHtml() before interpolation
  • MCP HTTP server hardened with a 1MB body size limit, 100-session cap, and automatic cleanup of sessions idle for more than 5 minutes
  • Bond locking (POST /v1/bonds/lock) now requires Ed25519 signature verification, matching the auth model on all other state-changing endpoints
  • Fastify upgraded to 5.8.2

Post-v0.3.0 hardening (Session 20):

  • Bond TTL capped at 24 hours (86400 seconds) — requests exceeding the cap are rejected with 400 TTL_TOO_LONG
  • Action payload capped at 4096 bytes — oversized payloads are rejected with 400 PAYLOAD_TOO_LARGE
  • SQLite WAL mode enabled for improved concurrent read performance
  • SQLite busy timeout set to 5 seconds — database operations wait instead of failing immediately when the database is locked

Post-v0.3.0 hardening (Session 21):

  • Fail-closed auth by default — all auth keys (AGENTGATE_REST_KEY, AGENTGATE_ADMIN_KEY, AGENTGATE_MCP_KEY, AGENTGATE_DASHBOARD_KEY) are now required unless AGENTGATE_DEV_MODE=true is explicitly set. Missing keys return 500 SERVER_MISCONFIGURED instead of silently skipping auth
  • SQLite CHECK constraints — database-level enforcement on bonds (amount_cents >= 0, outstanding_exposure_cents >= 0, slashed_cents >= 0, valid status enum), actions (exposure_cents >= 0, valid status enum), and identities (status IN ('active', 'banned')). Startup data validation catches violations in existing databases
  • Rate-limit bucket cleanup — expired action_execute_buckets entries (older than 60 seconds) are now pruned on the same 60-second interval as the sweeper and nonce cleanup
  • Demo echo route gatedPOST /v1/demo/echo is only registered when AGENTGATE_DEV_MODE=true; returns 404 in production
  • Market position filtering at DB levelresolveMarket() now uses json_extract(payload, '$.marketId') in the SQL query instead of loading all open positions into memory
  • Market deadline validationresolutionDeadline must be a valid future ISO 8601 timestamp (enforced via Zod .refine())
  • Payload size measured in bytes — the 4096 limit now uses Buffer.byteLength() instead of .length, correctly measuring multi-byte characters

Post-v0.3.0 hardening (Session 22):

  • Positive exposure requiredexposure_cents is now mandatory and must be a positive integer. Zero-stake actions are rejected with 400 INVALID_EXPOSURE
  • Prediction market payload validationmarket.position actions now require a JSON object with marketId and side (yes or no) before anything is persisted
  • Legacy malformed market rows no longer break resolutionresolveMarket() now filters malformed JSON safely instead of letting SQLite throw malformed JSON
  • Banned identities fully blocked from market writes — banned identities can no longer create markets or resolve markets they previously created
  • Final bond status reflects aggregate outcomes — once the last action settles, bond status is derived from cumulative settlement totals (slashed beats burned, which beats released)
  • Safer outbound defaults — the implicit outbound allowlist no longer grants arbitrary loopback ports
  • WAL-safe backups — startup backups now use SQLite’s backup API instead of copying only the main .sqlite file
  • Agent adapter auto-init fixAgentAdapter.lockBond(), executeBondedAction(), and market helpers now auto-create/load the identity before building signed request bodies
  • MCP log sanitization — MCP tool completion logs now record result shape/keys instead of dumping full tool payloads or outbound response bodies to stderr

Comprehensive Security Audit (Codex + Claude Code cross-audit, 3 passes):

The full src/ codebase was put through a structured security audit — 3 passes to clean, with each pass re-reading every source file and verifying prior fixes before looking for new issues.

  • Pass 1: 22 findings (6 critical/high, 10 medium, 6 low). All fixed: self-resolution bypass, market creator arbitrage, missing signature verification on market endpoints, adapter missing auth headers, 204 response crash, identity nonce replay, market resolution ordering, silent catch-all in settlement, bond expiration TOCTOU, dashboard auth weakness, protocol-relative redirect, malformed deadline NaN bypass, agent name path traversal, dashboard XSS via title length, weak request ID entropy, nonce recording order, SQLite error detection fragility, and documentation gaps.
  • Pass 2: 6 medium findings, 5 low. All fixed: upper bounds on exposure/bond amounts (1B cent cap), market deadline capped at 1 year, MCP transport close error logging, strict MCP schema types, and known-limitation documentation for accepted trade-offs.
  • Pass 3: Clean. No new findings. All prior fixes verified in place.

How To Verify

Run:

npm run lint
npm run test

Regression coverage added for:

  • adapter auto-identity creation
  • WAL-safe backups
  • banned market create/resolve attempts
  • mixed-outcome bond status precedence
  • explicit allowlisting for outbound HTTP tests
  • market position payload validation and malformed legacy row handling

Next Steps

  • Add a small admin repair tool for malformed legacy market.position rows so operators can settle or purge bad historical data explicitly
  • Add one end-to-end test for the startup backup path in src/index.ts to exercise the async backup call in the same flow production uses

Remote Deployment (Archived)

AgentGate was previously deployed to a DigitalOcean droplet at agentgate.run with Caddy reverse proxy, UFW firewall, pm2 process management, and UptimeRobot monitoring. The live instance was decommissioned in March 2026. The project runs fully on localhost — see the "Running Locally" section above.


Ecosystem

AgentGate is the core substrate in a larger family of reference projects. For a cold first run, start with the Governed WriteFile Demo; it is the narrowest proof path. The rest of this section is for readers who want adjacent reference implementations built on the same bond-and-slash model.

Reference Agents

# Agent What it proves Tests Repo
001 Bonded File Transform Deterministic verification — machine checks machine 22 agentgate-bonded-file-transform
002 File Guardian Command-based verification + rollback on failure 50 agentgate-bonded-file-guardian
003 Email Rewriter Human judgment in the loop 11 agentgate-bonded-email-rewriter
004 Red Team Simulator Adversarial probing — five stages from solo attacker to 9-agent coordinated swarms. Includes Sleeper Agent (v0.6.0). 330 agentgate-red-team-simulator
005 Recursive Verifier Proof-style verification — generates executable scripts, runs in sandbox, scores, iterates 149 agentgate-recursive-verifier
006 Incentive Wargame Stress-tests incentive rules with AI-generated economic strategies 301 agentgate-incentive-wargame

Governance Extensions

Project What it adds Status Repo
Governed WriteFile Demo Narrowest first-run path: identity -> bond -> authenticated governed write_file -> independent on-disk verification -> audit artifact v0.1.0 shipped agentgate-governed-writefile-demo
Delegation Identity Proof Bounded human-to-agent delegation with dual bonds and a 6-state machine v0.1.0 shipped agentgate-delegation-proof
MCP Firewall Governance proxy for MCP tool calls — bonds before forwarding, slashes on bad outcomes v0.1.0 shipped agentgate-mcp-firewall
Epistemic Poisoning Simulator Tests whether bond-and-slash can govern knowledge integrity Design stage

Writeups

Related

  • RestaRules — machine-readable agent conduct rules for restaurants. A different angle on agent governance: environment-published norms rather than substrate-enforced bonds.

Tech Stack

  • Language: TypeScript (100%)
  • Runtime: Node.js 20+
  • Web framework: Fastify
  • Database: SQLite via better-sqlite3
  • Validation: Zod
  • Testing: Vitest (123 tests)
  • MCP SDK: @modelcontextprotocol/sdk
  • CI: GitHub Actions (build, lint, and test on every push and PR to main)

Project Files

  • src/ — core server logic (Fastify API, service layer, database, signing, structured logging)
  • src/mcp/ — MCP server exposing 7 tools over stdio and Streamable HTTP transports
  • src/agent-adapter.ts — clean agent-facing interface that hides signing, nonces, and HTTP details
  • src/dashboard.ts — real-time HTML dashboard
  • src/backup.ts — automatic database backup on startup (keeps 5 most recent)
  • src/reputation.ts — reputation scoring and trust tier computation
  • test/ — 123 tests across 12 suites (API, MCP integration, prediction markets, sweeper, red team, outbound HTTP, dashboard, adapter, reputation, trust tier)
  • examples/ — demo agents and adapter demo
  • docs/threat-model.md — threat model (attacks, defenses, non-goals, assumptions)
  • docs/red-team-plan.md — 20 adversarial attack scenarios across 5 phases
  • docs/manifesto.md — "How I Built AgentGate" — the full story
  • .github/workflows/ci.yml — GitHub Actions CI (build, lint, and test on every push/PR)
  • AGENTS.md — conventions for AI coding agents
  • LICENSE — MIT License

Roadmap

Future modules under consideration (not yet in development):

  • ZK-proof bond verification — Anonymous agent identity with economic accountability. Agents prove they hold a valid bond via zero-knowledge proof without revealing which agent they are. Monitoring ZK-API (Buterin/Davide, 2026) and OpenAnonymity for integration-ready implementations.
  • Non-deterministic verifiers — LLM-as-judge, API state checks, and human review verification modes. The bond loop is verifier-agnostic by design.
  • Hardware-backed delegation — YubiKey/FIDO2/WebAuthn to authorize agent bonds, replacing OAuth.

License

MIT — see LICENSE.