agentgate-runtime-control 2.13.8 → 2.13.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/bin/agentgate.js +31 -13
- package/examples/protect-first-tool.mjs +83 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,9 +52,20 @@ docker run --rm -p 8787:8787 agentgate
|
|
|
52
52
|
|
|
53
53
|
### Examples
|
|
54
54
|
|
|
55
|
+
- `examples/protect-first-tool.mjs` — protect four real tools in one file (`read_customer` → ALLOW, `delete_customer` → ASK, `refund` → ASK/BLOCK by amount, `export_all` → BLOCK). Start here.
|
|
55
56
|
- `examples/refund-agent.mjs` — protect a real side-effecting refund tool.
|
|
56
57
|
- `examples/policy-bundle.mjs` — test and activate a versioned policy bundle.
|
|
57
58
|
|
|
59
|
+
### Test your own tools against Attack Lab
|
|
60
|
+
|
|
61
|
+
By default, `agentgate attack` runs the built-in attack scenarios against AgentGate's default policies. To test them against **your own** `agentgate.config.mjs` (the policies you actually ship with), pass `--config`:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
agentgate attack --config ./agentgate.config.mjs
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The config file must export an `agentgate` object created with `createAgentGate()` (exactly what `agentgate init` generates).
|
|
68
|
+
|
|
58
69
|
## Production readiness
|
|
59
70
|
|
|
60
71
|
See [`docs/production-readiness.md`](docs/production-readiness.md), [`docs/production-deployment.md`](docs/production-deployment.md), and [`docs/release-checklist.md`](docs/release-checklist.md) for deployment, operational, performance, and release gates.
|
package/bin/agentgate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import { evaluate } from '../src/policy-engine.js';
|
|
7
7
|
import { createAgentGate } from '../src/agentgate.js';
|
|
@@ -185,20 +185,38 @@ export { pack };
|
|
|
185
185
|
} else if (cmd === 'test' || cmd === 'check') {
|
|
186
186
|
console.log(JSON.stringify(evaluate({ action, amount: Number(amount) }), null, 2));
|
|
187
187
|
} else if (cmd === 'attack') {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
188
|
+
const configIndex = process.argv.indexOf('--config');
|
|
189
|
+
const configPath = configIndex >= 0 ? process.argv[configIndex + 1] : null;
|
|
190
|
+
const defaultTools = [
|
|
191
|
+
{ name: 'export_all', handler: async () => ({ executed: true }) },
|
|
192
|
+
{ name: 'update_production', handler: async () => ({ executed: true }) },
|
|
193
|
+
{ name: 'delete', handler: async () => ({ executed: true }) },
|
|
194
|
+
{ name: 'refund', handler: async args => ({ refunded: args.amount }) },
|
|
195
|
+
{ name: 'publish', handler: async () => ({ executed: true }) }
|
|
196
|
+
];
|
|
197
|
+
let gateway = null;
|
|
198
|
+
let label = 'built-in default policies';
|
|
199
|
+
if (configPath) {
|
|
200
|
+
const resolved = path.resolve(process.cwd(), configPath);
|
|
201
|
+
try {
|
|
202
|
+
const mod = await import(pathToFileURL(resolved).href);
|
|
203
|
+
const gate = mod.agentgate || mod.default;
|
|
204
|
+
if (!gate || typeof gate.withMCP !== 'function') {
|
|
205
|
+
console.error(`No 'agentgate' export found in ${configPath} (expected the object returned by createAgentGate()). Falling back to built-in policies.`);
|
|
206
|
+
} else {
|
|
207
|
+
gateway = gate.withMCP({ mode: 'enforce', tools: defaultTools });
|
|
208
|
+
label = `your policies (${configPath})`;
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error(`Could not load config ${configPath}: ${err.message}. Falling back to built-in policies.`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!gateway) {
|
|
215
|
+
gateway = createMCPGateway({ mode: 'enforce', policies: { productionBlock: true }, tools: defaultTools });
|
|
216
|
+
}
|
|
199
217
|
const results = await runGatewayAttackLab(gateway);
|
|
200
218
|
const summary = summarizeAttackResults(results);
|
|
201
|
-
console.log(
|
|
219
|
+
console.log(`\nAgentGate Attack Runner — testing against ${label}`);
|
|
202
220
|
console.table(results.map(x => ({ test: x.name, decision: x.decision, risk: x.risk, passed: x.passed, runId: x.runId })));
|
|
203
221
|
console.log('Summary:', JSON.stringify(summary));
|
|
204
222
|
process.exitCode = summary.failed ? 2 : 0;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Protect your first real tool with AgentGate.
|
|
2
|
+
//
|
|
3
|
+
// The flow AgentGate sits in:
|
|
4
|
+
//
|
|
5
|
+
// AI Agent ─────▶ AgentGate ─────▶ Tool / API ─────▶ External system
|
|
6
|
+
// (ALLOW/ASK/BLOCK
|
|
7
|
+
// decided here,
|
|
8
|
+
// BEFORE the tool
|
|
9
|
+
// handler runs)
|
|
10
|
+
//
|
|
11
|
+
// This example wraps four tools an agent might call and shows the
|
|
12
|
+
// decision each one gets under a simple, realistic policy:
|
|
13
|
+
//
|
|
14
|
+
// read_customer -> ALLOW (read-only, no side effects)
|
|
15
|
+
// delete_customer -> ASK (destructive, always needs a human to approve)
|
|
16
|
+
// refund -> ASK below $5,000, BLOCK above $5,000
|
|
17
|
+
// export_all -> BLOCK (bulk data export, always blocked)
|
|
18
|
+
//
|
|
19
|
+
// Note that "destructive" tools (delete, refund, publish, export_all,
|
|
20
|
+
// update_production, deploy) always require at least ASK — there is no
|
|
21
|
+
// amount small enough to skip approval entirely for them. That is
|
|
22
|
+
// deliberate: this policy engine treats "asks for approval" as the safe
|
|
23
|
+
// default for anything with a side effect, not just anything expensive.
|
|
24
|
+
//
|
|
25
|
+
// Run it with:
|
|
26
|
+
// node examples/protect-first-tool.mjs
|
|
27
|
+
|
|
28
|
+
import { createAgentGate } from 'agentgate-runtime-control';
|
|
29
|
+
|
|
30
|
+
const gate = createAgentGate({
|
|
31
|
+
agent: 'SupportAgent',
|
|
32
|
+
mode: 'enforce',
|
|
33
|
+
policies: {
|
|
34
|
+
productionBlock: true, // in production, destructive actions are blocked outright (not even ASK)
|
|
35
|
+
approvalAmount: 5000, // refunds above this amount are always BLOCKed, no approval possible
|
|
36
|
+
blockActions: ['export_all'] // export_all is blocked regardless of amount/environment
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// --- The real tool handlers your agent would otherwise call directly ---
|
|
41
|
+
async function readCustomer({ customerId }) {
|
|
42
|
+
return { customerId, name: 'Jane Doe', plan: 'pro' };
|
|
43
|
+
}
|
|
44
|
+
async function deleteCustomer({ customerId }) {
|
|
45
|
+
return { deleted: customerId };
|
|
46
|
+
}
|
|
47
|
+
async function refund({ amount, customerId }) {
|
|
48
|
+
return { refunded: amount, customerId };
|
|
49
|
+
}
|
|
50
|
+
async function exportAll() {
|
|
51
|
+
return { exportedRows: 1_000_000 };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Wrap each one with agentgate.protect() before the agent ever sees it ---
|
|
55
|
+
const protectedReadCustomer = gate.protect(readCustomer, { tool: 'read_customer', action: 'read' });
|
|
56
|
+
const protectedDeleteCustomer = gate.protect(deleteCustomer, { tool: 'delete_customer', action: 'delete' });
|
|
57
|
+
const protectedRefund = gate.protect(refund, { tool: 'refund', action: 'refund' });
|
|
58
|
+
const protectedExportAll = gate.protect(exportAll, { tool: 'export_all', action: 'export_all' });
|
|
59
|
+
|
|
60
|
+
async function tryCall(label, fn, input) {
|
|
61
|
+
try {
|
|
62
|
+
const result = await fn(input, input);
|
|
63
|
+
console.log(`${label.padEnd(24)} -> ${result.status.toUpperCase()} (${result.agentgate.decision}) — ${result.agentgate.reason}`);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.log(`${label.padEnd(24)} -> BLOCKED (${error.agentgate?.decision}) — ${error.agentgate?.reason}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// environment: 'development' lets the amount/action-based rules below decide
|
|
70
|
+
// (ASK vs ALLOW vs BLOCK). In production, productionBlock:true would block
|
|
71
|
+
// every destructive action outright — try changing this to 'production' and
|
|
72
|
+
// re-running to see that stricter behavior.
|
|
73
|
+
const env = 'development';
|
|
74
|
+
|
|
75
|
+
console.log('AgentGate — protecting four real tools\n');
|
|
76
|
+
await tryCall('read_customer', protectedReadCustomer, { customerId: 'cus_123', environment: env });
|
|
77
|
+
await tryCall('delete_customer', protectedDeleteCustomer, { customerId: 'cus_123', environment: env });
|
|
78
|
+
await tryCall('refund (small)', protectedRefund, { amount: 250, customerId: 'cus_123', environment: env });
|
|
79
|
+
await tryCall('refund (large)', protectedRefund, { amount: 8000, customerId: 'cus_123', environment: env });
|
|
80
|
+
await tryCall('export_all', protectedExportAll, { environment: env });
|
|
81
|
+
|
|
82
|
+
console.log('\nNothing above BLOCK or pending ASK ever reached its real handler.');
|
|
83
|
+
console.log('See gate.approvals() to review and approve pending ASK requests.');
|