You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+21-13Lines changed: 21 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ As AI agents reduce the marginal cost of sending bids, API calls, negotiations,
10
10
11
11
Traditional defenses don't solve this. Rate limits cap volume but don't make bad actions costly. Auth tokens verify identity but don't require skin in the game. Policy engines enforce rules but can't make an agent economically accountable for its behavior.
12
12
13
-
AgentGate takes a different approach: **before an agent can execute a high-impact action, it must post a bond as collateral.** If the action succeeds, the bond is released. If the agent behaves maliciously, the bond is slashed. This makes bad behavior economically irrational — the agent loses more than it gains.
13
+
AgentGate takes a different approach: **before an agent can execute a high-impact action, it must post a bond as collateral.** If the action resolves cleanly, the reserved exposure is settled and released. If the agent behaves maliciously, the reserved exposure is slashed. This makes bad behavior economically irrational — the agent loses more than it gains.
14
14
15
15
AgentGate sits as a deterministic choke point between autonomous agents and external actions (market orders, API calls, financial operations), enforcing economic accountability through signed identities and reusable bond-based exposure tracking.
Outcome must be one of: `success`, `failed`, or `malicious`. On success/failed, exposure is released. On malicious, the bond is slashed.
87
+
Outcome must be one of: `success`, `failed`, or `malicious`. On `success` or `failed`, that action's reserved exposure is settled and released. On `malicious`, that action's reserved exposure is slashed from the bond.
88
88
89
89
### Common Errors
90
90
91
91
| Error | Cause | Fix |
92
92
|---|---|---|
93
-
|`INVALID_SIGNATURE`|Signature doesn't match body + timestamp| Verify you're signing `sha256(nonce + method + path + timestamp + JSON.stringify(body))` with the correct private key |
94
-
|`TIMESTAMP_EXPIRED`|Timestamp is older than 60 seconds | Use a fresh timestamp for each request |
93
+
|`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|
94
+
|`MISSING_NONCE`|`x-nonce` header is missing | Send a fresh nonce on every POST request |
95
95
|`DUPLICATE_NONCE`| Same nonce reused by the same identity | Generate a fresh UUID for every request |
96
96
|`TIER_BOND_CAP_EXCEEDED`| Bond amount exceeds identity's trust tier cap | Build reputation with successful resolutions to unlock higher tiers |
97
97
|`INSUFFICIENT_BOND_CAPACITY`| Bond doesn't have enough remaining capacity | Lock a larger bond or resolve outstanding actions to free capacity |
@@ -125,16 +125,18 @@ Bonds are not single-use. Each bond represents reusable execution capacity.
-**TTL cap:** maximum 86400 seconds (24 hours) — requests exceeding the cap are rejected
128
-
- Bond status lifecycle: `active` → `occupied` (when action attached) → `released` / `burned` / `slashed`
128
+
- Bond status lifecycle during normal settlement: `active` → `occupied` (when action attached) → `released` / `burned` / `slashed`
129
+
- Idle bonds that are touched after their TTL has elapsed are marked `expired`; expired bonds with open actions are handled by the sweeper
129
130
130
131
### Exposure Lifecycle
131
132
132
133
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.
133
134
134
135
-**Execute:** exposure reserved, `outstanding_exposure_cents` incremented, bond marked `occupied`
135
-
-**Resolve (success/failed):** that action's exposure released; `refund_cents` accumulated on the bond; bond returns to `active` only when all open actions are resolved
136
-
-**Resolve (malicious):**`amount_cents` reduced (clamped at zero), `slashed_cents` and `burned_cents` increased; bond `burned` only when no open actions remain
137
-
-**Settlement accounting:**`refund_cents`, `burned_cents`, `slashed_cents`, and `closed_at` (ISO timestamp) are persisted on the bond record at resolution time
136
+
-**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`
137
+
-**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`
138
+
-**Resolve (malicious):** that action's exposure released; `amount_cents` reduced (clamped at zero) and `slashed_cents` increased; when the last open action settles, the bond closes as `slashed`
139
+
-**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
138
140
139
141
### Auto-Slash Sweeper
140
142
@@ -153,7 +155,7 @@ The dashboard shows per-identity scores with color coding (green for positive, r
153
155
| Tier | Label | Requirement | Bond Cap |
154
156
|---|---|---|---|
155
157
| 1 | New | Default | 100¢ |
156
-
| 2 | Established | 5 qualifying successes from 5 distinct resolvers, 0 malicious | 500¢ |
158
+
| 2 | Established | 5 qualifying successes from 2 distinct resolvers, 0 malicious | 500¢ |
157
159
| 3 | Trusted | 20 qualifying successes from 20 distinct resolvers, 0 malicious | No tier cap (normal capacity rules) |
158
160
159
161
- Any malicious resolution forces immediate demotion to Tier 1
@@ -229,7 +231,7 @@ AgentGate includes a prediction market demo that illustrates multi-agent economi
229
231
230
232
1. An operator creates a market with a yes/no question and a resolution deadline (must be a valid future ISO 8601 timestamp)
231
233
2. Agents take positions by executing a `market.position` action against a locked bond, declaring a `side` of `yes` or `no`
232
-
3. When the market resolves, all open positions are settled automatically — winners' bonds are released, losers' bonds are burned
234
+
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
233
235
234
236
**REST endpoints:**
235
237
@@ -238,13 +240,19 @@ AgentGate includes a prediction market demo that illustrates multi-agent economi
238
240
curl -s http://127.0.0.1:3000/markets \
239
241
-H 'content-type: application/json' \
240
242
-H 'x-agentgate-key: YOUR_KEY' \
241
-
-d '{ "question": "Will BTC hit 100k by Friday?", "resolutionDeadline": "2025-01-10T00:00:00Z" }'
243
+
-H "x-agentgate-timestamp: $TIMESTAMP" \
244
+
-H "x-agentgate-signature: $SIGNATURE" \
245
+
-H "x-nonce: $(uuidgen)" \
246
+
-d '{ "creatorId": "id_abc123", "question": "Will BTC hit 100k by Friday?", "resolutionDeadline": "2026-04-20T00:00:00Z" }'
Copy file name to clipboardExpand all lines: docs/threat-model.md
+19-23Lines changed: 19 additions & 23 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -37,10 +37,9 @@ Rate limits cap volume. Auth gates cap access. AgentGate caps economic exposure.
37
37
**How AgentGate resists this:**
38
38
39
39
- All state-changing requests include a millisecond timestamp in the `x-agentgate-timestamp` header.
40
+
- All state-changing requests also require an `x-nonce` header, and nonces are stored per identity and rejected on reuse.
40
41
- Requests older than 60 seconds are rejected.
41
-
- The signature covers the timestamp + request body, so modifying either invalidates the signature.
42
-
43
-
**Known gap:** There is no nonce store. Within the 60-second window, a replayed request with an identical timestamp and body would pass verification. A nonce-based deduplication layer is a future improvement.
42
+
- The signature covers `nonce + method + path + timestamp + JSON.stringify(body)`, so tampering with any signed field invalidates the request.
44
43
45
44
### 3. Forged or Tampered Requests
46
45
@@ -49,7 +48,9 @@ Rate limits cap volume. Auth gates cap access. AgentGate caps economic exposure.
49
48
**How AgentGate resists this:**
50
49
51
50
- All state-changing endpoints require an Ed25519 signature.
52
-
- The signed message is: `sha256(timestamp + JSON.stringify(body))`.
51
+
- Identity registration itself requires proof-of-possession: the caller must sign the registration request with the private key matching the public key being registered.
52
+
- Public keys are unique at the database level, so the same key cannot register multiple identities.
53
+
- The signed message is: `sha256(nonce + method + path + timestamp + JSON.stringify(body))`.
53
54
- The signature is verified against the registered public key for that identity.
54
55
- Ed25519 is a strong, well-studied cryptographic scheme — forging a signature without the private key is computationally infeasible.
55
56
@@ -72,7 +73,7 @@ Rate limits cap volume. Auth gates cap access. AgentGate caps economic exposure.
72
73
**How AgentGate resists this:**
73
74
74
75
- Actions must be explicitly resolved (success, failed, or malicious).
75
-
- If resolved as malicious: the bond's `amount_cents` is reduced (clamped at zero), `slashed_cents` is increased, and the bond is burned.
76
+
- If resolved as malicious: the action's reserved exposure is slashed, the bond's `amount_cents` is reduced (clamped at zero), and `slashed_cents` is increased. Once the last open action settles, the bond closes as `slashed`.
76
77
- The reputation system penalizes malicious actions heavily: -20 points per malicious resolution vs. +10 for success.
77
78
- An agent's reputation score follows its identity permanently — there is no way to "reset" a damaged score except by building a long track record of good behavior.
78
79
@@ -84,26 +85,22 @@ Being honest about limitations is as important as describing defenses. AgentGate
84
85
85
86
### Bond Expiry Enforcement
86
87
87
-
Bonds have a `ttl_seconds` field, but expiry is only checked when an action tries to use the bond. There is no background process that automatically expires or releases bonds when their TTL elapses. An expired bond sits in "active" status until something touches it.
88
+
Bonds with open actions are swept every 60 seconds: if the bond TTL has elapsed while an action is still open, AgentGate auto-resolves that action as `malicious`. But idle bonds are not expired by a separate background pass; an unused expired bond is marked `expired` when something tries to use it.
88
89
89
-
**Impact:**Low risk in current single-user local deployment. Higher risk in multi-agent or multi-tenant scenarios.
90
+
**Impact:**Honest but slightly asymmetric lifecycle behavior. The economic guarantee is enforced for open actions, but unused expired bonds remain lazily marked until touched.
90
91
91
92
### Auto-Slash on Timeout
92
93
93
-
If an action is executed but never resolved (the agent crashes, disconnects, or simply ignores the resolution step), the action stays open indefinitely. There is no sweeper process that detects timed-out actions and automatically slashes the bond.
94
+
AgentGate does auto-slash unresolved actions, but only on bond TTL expiry. There is no separate per-action timeout shorter than the bond's TTL.
94
95
95
-
**Impact:**This is the biggest gap in the economic model. An agent can tie up bond capacity forever by executing actions and never resolving them. This is the highest-priority planned fix.
96
+
**Impact:**Timeout behavior is tied to bond design. A long-lived bond allows a long-lived unresolved action; a short-lived bond forces faster settlement.
96
97
97
98
### Multi-Instance / Distributed Deployment
98
99
99
100
AgentGate uses SQLite with in-memory assumptions. Running multiple Node.js processes against the same database will produce race conditions and incorrect exposure tracking. This is a single-instance system.
100
101
101
102
**Impact:** Fine for local development and single-server deployment. Not suitable for distributed or high-availability setups without architectural changes.
102
103
103
-
### Identity Revocation
104
-
105
-
There is currently no mechanism to revoke or ban an identity. A malicious identity with a slashed reputation can still create new bonds and attempt actions (as long as it has the collateral). The progressive minimum bond and reputation score make this increasingly expensive, but there is no hard ban.
106
-
107
104
### Real Economic Collateral
108
105
109
106
Bonds are denominated in cents but are not backed by real money, cryptocurrency, or any external payment system. The collateral is purely internal accounting. AgentGate enforces the *economic logic* of bonding, but does not yet connect to real-world value transfer.
@@ -125,13 +122,13 @@ AgentGate does not handle TLS termination, DDoS protection, or network-layer sec
| Unresolved action timeout | Background sweeper + auto-slash | ✅ Implemented — via `sweepExpiredActions()` in service.ts — runs every 60 seconds, slashes bonds whose TTL has expired with unresolved actions |
133
-
| Bond auto-expiry | TTL enforcement via background process | ✅ Implemented — via `sweepExpiredActions()` in service.ts — runs every 60 seconds, slashes bonds whose TTL has expired with unresolved actions|
134
-
| Identity revocation |Ban list or revocation mechanism | 📋 Future|
130
+
| Bond auto-expiry | TTL enforcement on use, plus sweeper for expired bonds with open actions | ⚠️ Partial — open actions are swept; idle expired bonds are marked on next use|
@@ -141,13 +138,13 @@ AgentGate does not handle TLS termination, DDoS protection, or network-layer sec
141
138
142
139
## Known Limitations
143
140
144
-
### Identity Model Does Not Enforce Public Key Uniqueness or Proof-of-Possession
141
+
### Identity Creation Is Unique-Per-Key but Still Cheap Across Fresh Keys
145
142
146
-
The current identity registration endpoint (`POST /v1/identities`) accepts any valid Ed25519 publickey and creates a new identity for it — without checking whether that key is already registered and without requiring the caller to prove they hold the corresponding private key.
143
+
The current identity registration endpoint (`POST /v1/identities`) does enforce proof-of-possession and public-key uniqueness: the caller must sign the registration request with the matching private key, and the same public key cannot be registered twice.
147
144
148
145
This means:
149
146
150
-
-**A single actor can create multiple identities**using the same public key, or different keys they control, spreading activity across them.
147
+
-**A single actor can still create multiple identities**by generating fresh keypairs they control.
151
148
-**Reputation tracking is diluted.** A bad actor with a -40 reputation score can create a fresh identity and start over at 0.
152
149
-**Per-identity rate limits (10 actions/60s) can be circumvented** by rotating across identities.
153
150
-**The 3-malicious-actions auto-ban threshold resets** with each new identity, so an attacker is never permanently banned — only temporarily inconvenienced.
@@ -156,9 +153,8 @@ This means:
156
153
157
154
**Future hardening options:**
158
155
159
-
1.**Unique index on `public_key`** — prevent the same key from registering multiple identities.
160
-
2.**Signed registration challenges (proof-of-possession)** — require the caller to sign a server-issued challenge during `POST /v1/identities`, proving they hold the private key. This prevents identity squatting on keys the caller doesn't control.
161
-
3.**Key-fingerprint-scoped enforcement** — move bans, rate limits, and reputation scoring from `identity_id` to the public key fingerprint. This way, creating a new identity with the same key inherits the existing reputation and restrictions, closing the reset loophole entirely.
156
+
1.**External identity binding** — tie keys to proof-of-stake, KYC, or other scarce credentials.
157
+
2.**Cross-key reputation linkages** — add stronger operator-side heuristics or attestations for related identities when the deployment warrants it.
0 commit comments