@dinpd/ai-agent-guard 0.1.0

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 ADDED
@@ -0,0 +1,234 @@
1
+ # @dinpd/ai-agent-guard
2
+
3
+ Dependency-free runtime guard for AI agent tool calls.
4
+
5
+ Use this package before an agent executes a tool, API call, browser action,
6
+ message send, payment, refund, export, or production change.
7
+
8
+ Install it from npm:
9
+
10
+ ```bash
11
+ npm install @dinpd/ai-agent-guard
12
+ ```
13
+
14
+ The first use case is simple: put a circuit breaker and approval gate in front
15
+ of your agent's tools so loops, spend spikes, duplicate side effects, and PII
16
+ egress are caught before execution.
17
+
18
+ The guard returns one of three decisions:
19
+
20
+ - `allow`: execute the tool call.
21
+ - `challenge_required`: pause and ask for approval.
22
+ - `deny`: block execution.
23
+
24
+ ## Five-Minute Path
25
+
26
+ Install the package:
27
+
28
+ ```bash
29
+ npm install @dinpd/ai-agent-guard
30
+ ```
31
+
32
+ Run the local demos:
33
+
34
+ ```bash
35
+ git clone https://github.com/dinpd/AgentPass.git
36
+ cd AgentPass/packages/guard
37
+ npm install
38
+ npm run demo:quickstart
39
+ npm run demo:mcp
40
+ ```
41
+
42
+ The quickstart demo shows the intended first integration:
43
+
44
+ 1. A normal tool call executes.
45
+ 2. A repeated tool call is allowed once.
46
+ 3. The third identical call is denied.
47
+ 4. A PII email pauses for approval.
48
+
49
+ Copy one of the starter policies and tighten it for your agent:
50
+
51
+ - [`policies/tool-spend-cap.json`](policies/tool-spend-cap.json): cap tool calls,
52
+ retries, tokens, runtime, and estimated cost per job.
53
+ - [`policies/pii-egress.json`](policies/pii-egress.json): restrict PII movement
54
+ to approved destinations and block high-risk fields.
55
+ - [`policies/refund-payment.json`](policies/refund-payment.json): require
56
+ approval, amount caps, idempotency keys, and single-use execution.
57
+ - [`policies/shell-browser-guard.json`](policies/shell-browser-guard.json):
58
+ challenge shell/file/browser actions and block secrets in external flows.
59
+ - [`policies/mcp-tool-gateway.json`](policies/mcp-tool-gateway.json): start a
60
+ provider-style MCP tool policy with reads, writes, credits, email, and PII
61
+ flows.
62
+
63
+ ## Copy-Paste Wrapper
64
+
65
+ ```ts
66
+ import { createToolGate } from "@dinpd/ai-agent-guard";
67
+
68
+ const gate = createToolGate({
69
+ policy: {
70
+ tools: {
71
+ "web.search": { action: "read" }
72
+ },
73
+ budgets: {
74
+ maxIdenticalToolCallsPerJob: 2,
75
+ maxEstimatedCostUsdPerJob: 1
76
+ }
77
+ }
78
+ });
79
+
80
+ async function runAgentTool(toolCall) {
81
+ const execution = await gate.run(
82
+ {
83
+ agentId: "research-agent",
84
+ jobId: toolCall.jobId,
85
+ tool: toolCall.name,
86
+ action: "read",
87
+ resource: toolCall.query,
88
+ callFingerprint: `${toolCall.name}:${toolCall.query}`,
89
+ estimatedTokens: toolCall.estimatedTokens,
90
+ estimatedCostUsd: toolCall.estimatedCostUsd
91
+ },
92
+ () => executeTool(toolCall)
93
+ );
94
+
95
+ if (!execution.executed) {
96
+ return execution.decision;
97
+ }
98
+
99
+ return execution.result;
100
+ }
101
+ ```
102
+
103
+ ## Tool Gate
104
+
105
+ Use `createToolGate` when you want AgentPass to sit directly in front of tool
106
+ execution:
107
+
108
+ ```ts
109
+ import { createToolGate } from "@dinpd/ai-agent-guard";
110
+
111
+ const gate = createToolGate({ policy });
112
+
113
+ const execution = await gate.run(
114
+ {
115
+ agentId: "support-agent",
116
+ jobId: "case-1042",
117
+ tool: "stripe.refund",
118
+ action: "pay",
119
+ resource: "payment/pi_123",
120
+ amountUsd: 49,
121
+ idempotencyKey: "refund-case-1042-pi_123"
122
+ },
123
+ () => stripe.refunds.create({ payment_intent: "pi_123", amount: 4900 })
124
+ );
125
+
126
+ if (!execution.executed) {
127
+ return execution.decision;
128
+ }
129
+
130
+ return execution.result;
131
+ ```
132
+
133
+ ## MCP Tool-Call Gate
134
+
135
+ Use `createMcpToolGate` when you want to guard MCP `tools/call` requests before
136
+ forwarding them to a provider or internal MCP server:
137
+
138
+ ```ts
139
+ import { createMcpToolGate } from "@dinpd/ai-agent-guard";
140
+
141
+ const gate = createMcpToolGate({
142
+ policy,
143
+ mappings: {
144
+ "provider.billing.issue_credit": {
145
+ resource: (args) => `provider/customer/${String(args.customerId)}`,
146
+ amountUsd: (args) => Number(args.amountUsd),
147
+ idempotencyKey: (args) => String(args.idempotencyKey)
148
+ }
149
+ }
150
+ });
151
+
152
+ const execution = await gate.run(
153
+ {
154
+ params: {
155
+ name: "provider.billing.issue_credit",
156
+ arguments: {
157
+ customerId: "cus_123",
158
+ amountUsd: 49,
159
+ idempotencyKey: "credit-case-1042-cus_123"
160
+ }
161
+ }
162
+ },
163
+ {
164
+ agentId: "support-agent",
165
+ jobId: "case-1042",
166
+ userId: "user-17"
167
+ },
168
+ ({ call }) => forwardMcpToolCall(call)
169
+ );
170
+
171
+ if (!execution.executed) {
172
+ return execution.decision;
173
+ }
174
+ ```
175
+
176
+ The MCP adapter is dependency-free. It accepts a plain MCP-style `{ params:
177
+ { name, arguments } }` object, maps arguments into an AgentPass guard check, and
178
+ uses the same `allow` / `deny` / `challenge_required` result as the local tool
179
+ gate.
180
+
181
+ ## What It Checks
182
+
183
+ - Closed-world tool declarations
184
+ - Tool/action mismatches
185
+ - Approval requirements
186
+ - Amount caps
187
+ - Idempotency keys and single-use actions
188
+ - PII/sensitive-data movement to unsafe destinations
189
+ - Field allowlists and blocked fields
190
+ - Destination domain allowlists
191
+ - Per-job tool-call, same-tool, identical-call, retry, token, cost, and runtime budgets
192
+ - Soft budget thresholds that return `challenge_required` before hard denial
193
+ - Optional `callFingerprint` values for detecting repeated tool calls without
194
+ storing full tool parameters
195
+
196
+ This package is intentionally local and in-memory for the initial package.
197
+ Persistent approvals, policy distribution, shared counters, and audit export
198
+ belong in the runtime service layer.
199
+
200
+ ## Local Demo
201
+
202
+ ```bash
203
+ npm install
204
+ npm test
205
+ npm run demo:quickstart
206
+ npm run demo:mcp
207
+ npm run demo
208
+ npm run demo:circuit
209
+ npm run demo:gate
210
+ npm run demo:pii
211
+ ```
212
+
213
+ The refund demo shows the initial runtime-guard story:
214
+
215
+ 1. A support agent proposes a refund.
216
+ 2. The guard returns `challenge_required`.
217
+ 3. The approved refund succeeds once.
218
+ 4. A retry with the same idempotency key is denied.
219
+ 5. A PII email to an unapproved destination is denied.
220
+
221
+ The circuit-breaker demo shows tool-thrashing and spend controls:
222
+
223
+ 1. Repeated identical tool calls are denied.
224
+ 2. Soft token/cost thresholds pause for approval.
225
+ 3. Hard token/cost caps deny execution even after approval.
226
+
227
+ The PII demo shows destination-specific data movement rules:
228
+
229
+ 1. CRM PII read into agent context is allowed.
230
+ 2. Customer email requires approval.
231
+ 3. Unknown webhook destinations are denied.
232
+ 4. Raw PII prompts to model providers are denied.
233
+ 5. Bulk file exports are capped by record count.
234
+ 6. High-risk fields are blocked for browser automation.
@@ -0,0 +1,214 @@
1
+ export type AgentAction = "read" | "write" | "send" | "delete" | "pay" | "deploy" | "export" | "admin" | string;
2
+ export type DecisionType = "allow" | "deny" | "challenge_required";
3
+ export type ToolPolicy = {
4
+ action?: AgentAction;
5
+ requiresApproval?: boolean;
6
+ requiresApprovalIfPii?: boolean;
7
+ maxAmountUsd?: number;
8
+ requireIdempotencyKey?: boolean;
9
+ singleUse?: boolean;
10
+ allowedDomains?: string[];
11
+ blockedFields?: string[];
12
+ allowedFields?: string[];
13
+ };
14
+ export type FlowPolicy = {
15
+ from: string;
16
+ to: string;
17
+ destinationType?: string;
18
+ decision?: "allow" | "deny";
19
+ requiresApproval?: boolean;
20
+ dataClassification?: string[];
21
+ allowedDomains?: string[];
22
+ blockedFields?: string[];
23
+ allowedFields?: string[];
24
+ maxRecords?: number;
25
+ };
26
+ export type BudgetPolicy = {
27
+ challengeAfterToolCallsPerJob?: number;
28
+ challengeAfterTokensPerJob?: number;
29
+ challengeAfterEstimatedCostUsdPerJob?: number;
30
+ challengeAfterRuntimeMsPerJob?: number;
31
+ maxToolCallsPerJob?: number;
32
+ maxSameToolCallsPerJob?: number;
33
+ maxIdenticalToolCallsPerJob?: number;
34
+ maxRetriesPerTool?: number;
35
+ maxTokensPerJob?: number;
36
+ maxEstimatedCostUsdPerJob?: number;
37
+ maxRuntimeMsPerJob?: number;
38
+ };
39
+ export type GuardPolicy = {
40
+ tools?: Record<string, ToolPolicy>;
41
+ flows?: FlowPolicy[];
42
+ budgets?: BudgetPolicy;
43
+ defaultSensitiveDestinationDecision?: "allow" | "deny" | "challenge_required";
44
+ sensitiveClassifications?: string[];
45
+ sensitiveDestinationTypes?: string[];
46
+ };
47
+ export type GuardCheck = {
48
+ agentId: string;
49
+ tool: string;
50
+ action: AgentAction;
51
+ jobId?: string;
52
+ userId?: string;
53
+ resource?: string;
54
+ callFingerprint?: string;
55
+ amountUsd?: number;
56
+ idempotencyKey?: string;
57
+ retryCount?: number;
58
+ approvalId?: string;
59
+ dataFrom?: string;
60
+ dataTo?: string;
61
+ destinationType?: string;
62
+ externalDomain?: string;
63
+ dataClassification?: string[];
64
+ fieldSet?: string[];
65
+ recordCount?: number;
66
+ estimatedTokens?: number;
67
+ estimatedCostUsd?: number;
68
+ };
69
+ export type GuardDecision = {
70
+ type: DecisionType;
71
+ allow: boolean;
72
+ challengeRequired: boolean;
73
+ reasons: string[];
74
+ challenge?: GuardChallenge;
75
+ event: GuardDecisionEvent;
76
+ };
77
+ export type GuardChallenge = {
78
+ reason: string;
79
+ requiredApprovalFor: Array<"tool" | "flow" | "pii" | "budget">;
80
+ tool: string;
81
+ action: AgentAction;
82
+ resource?: string;
83
+ amountUsd?: number;
84
+ dataFrom?: string;
85
+ dataTo?: string;
86
+ externalDomain?: string;
87
+ };
88
+ export type GuardDecisionEvent = {
89
+ decisionId: string;
90
+ decision: DecisionType;
91
+ allowed: boolean;
92
+ reasons: string[];
93
+ agentId: string;
94
+ tool: string;
95
+ action: AgentAction;
96
+ jobId?: string;
97
+ userId?: string;
98
+ resource?: string;
99
+ callFingerprint?: string;
100
+ amountUsd?: number;
101
+ idempotencyKey?: string;
102
+ approvalId?: string;
103
+ dataFrom?: string;
104
+ dataTo?: string;
105
+ destinationType?: string;
106
+ externalDomain?: string;
107
+ dataClassification: string[];
108
+ fieldSet: string[];
109
+ recordCount?: number;
110
+ estimatedTokens?: number;
111
+ estimatedCostUsd?: number;
112
+ issuedAt: string;
113
+ };
114
+ export type AgentPassGuardOptions = {
115
+ policy: GuardPolicy;
116
+ now?: () => Date;
117
+ idGenerator?: () => string;
118
+ };
119
+ export type ToolExecutionContext = {
120
+ check: GuardCheck;
121
+ decision: GuardDecision;
122
+ };
123
+ export type GuardedToolExecutor<TResult> = (context: ToolExecutionContext) => TResult | Promise<TResult>;
124
+ export type GuardedToolExecutionResult<TResult> = {
125
+ executed: true;
126
+ decision: GuardDecision;
127
+ result: TResult;
128
+ } | {
129
+ executed: false;
130
+ decision: GuardDecision;
131
+ result?: never;
132
+ };
133
+ export type AgentPassToolGateOptions = AgentPassGuardOptions | {
134
+ guard: AgentPassGuard;
135
+ };
136
+ export type McpToolCall = {
137
+ name: string;
138
+ arguments?: Record<string, unknown>;
139
+ };
140
+ export type McpToolsCallRequest = {
141
+ params: McpToolCall;
142
+ };
143
+ export type McpGuardContext = {
144
+ agentId: string;
145
+ jobId?: string;
146
+ userId?: string;
147
+ approvalId?: string;
148
+ retryCount?: number;
149
+ };
150
+ type McpMappedValue<T> = T | ((args: Record<string, unknown>, call: McpToolCall, context: McpGuardContext) => T | undefined);
151
+ export type McpToolMapping = {
152
+ action?: McpMappedValue<AgentAction>;
153
+ resource?: McpMappedValue<string>;
154
+ callFingerprint?: McpMappedValue<string>;
155
+ amountUsd?: McpMappedValue<number>;
156
+ idempotencyKey?: McpMappedValue<string>;
157
+ dataFrom?: McpMappedValue<string>;
158
+ dataTo?: McpMappedValue<string>;
159
+ destinationType?: McpMappedValue<string>;
160
+ externalDomain?: McpMappedValue<string>;
161
+ dataClassification?: McpMappedValue<string[]>;
162
+ fieldSet?: McpMappedValue<string[]>;
163
+ recordCount?: McpMappedValue<number>;
164
+ estimatedTokens?: McpMappedValue<number>;
165
+ estimatedCostUsd?: McpMappedValue<number>;
166
+ };
167
+ export type McpToolCallAdapterOptions = {
168
+ mappings?: Record<string, McpToolMapping>;
169
+ defaultAction?: AgentAction;
170
+ };
171
+ export type AgentPassMcpToolGateOptions = AgentPassToolGateOptions & McpToolCallAdapterOptions;
172
+ export type McpToolExecutionContext = ToolExecutionContext & {
173
+ call: McpToolCall;
174
+ arguments: Record<string, unknown>;
175
+ };
176
+ export type McpToolExecutor<TResult> = (context: McpToolExecutionContext) => TResult | Promise<TResult>;
177
+ export declare class AgentPassGuard {
178
+ private policy;
179
+ private now;
180
+ private idGenerator;
181
+ private usedIdempotencyKeys;
182
+ private usageByJob;
183
+ constructor(options: AgentPassGuardOptions);
184
+ check(input: GuardCheck): GuardDecision;
185
+ reset(): void;
186
+ private evaluateToolPolicy;
187
+ private evaluateFlowPolicy;
188
+ private evaluateBudgetPolicy;
189
+ private commit;
190
+ private decision;
191
+ private event;
192
+ }
193
+ export declare function createGuard(options: AgentPassGuardOptions): AgentPassGuard;
194
+ export declare class AgentPassToolGate {
195
+ readonly guard: AgentPassGuard;
196
+ constructor(options: AgentPassToolGateOptions);
197
+ check(input: GuardCheck): GuardDecision;
198
+ run<TResult>(input: GuardCheck, execute: GuardedToolExecutor<TResult>): Promise<GuardedToolExecutionResult<TResult>>;
199
+ reset(): void;
200
+ }
201
+ export declare function createToolGate(options: AgentPassToolGateOptions): AgentPassToolGate;
202
+ export declare class AgentPassMcpToolGate {
203
+ readonly gate: AgentPassToolGate;
204
+ private mappings;
205
+ private defaultAction;
206
+ constructor(options: AgentPassMcpToolGateOptions);
207
+ check(callOrRequest: McpToolCall | McpToolsCallRequest, context: McpGuardContext): GuardDecision;
208
+ run<TResult>(callOrRequest: McpToolCall | McpToolsCallRequest, context: McpGuardContext, execute: McpToolExecutor<TResult>): Promise<GuardedToolExecutionResult<TResult>>;
209
+ toGuardCheck(callOrRequest: McpToolCall | McpToolsCallRequest, context: McpGuardContext): GuardCheck;
210
+ reset(): void;
211
+ }
212
+ export declare function createMcpToolGate(options: AgentPassMcpToolGateOptions): AgentPassMcpToolGate;
213
+ export declare function mcpToolCallToGuardCheck(callOrRequest: McpToolCall | McpToolsCallRequest, context: McpGuardContext, options?: McpToolCallAdapterOptions): GuardCheck;
214
+ export {};