@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.
@@ -0,0 +1,155 @@
1
+ import { createMcpToolGate, type GuardPolicy } from "../src/index.ts";
2
+
3
+ const policy: GuardPolicy = {
4
+ tools: {
5
+ "provider.crm.search_customer": {
6
+ action: "read",
7
+ },
8
+ "provider.billing.issue_credit": {
9
+ action: "pay",
10
+ requiresApproval: true,
11
+ maxAmountUsd: 100,
12
+ requireIdempotencyKey: true,
13
+ singleUse: true,
14
+ },
15
+ "provider.email.send_external": {
16
+ action: "send",
17
+ requiresApprovalIfPii: true,
18
+ allowedDomains: ["customer.example"],
19
+ blockedFields: ["ssn", "access_token", "payment_method"],
20
+ },
21
+ },
22
+ flows: [
23
+ {
24
+ from: "provider_crm",
25
+ to: "agent_context",
26
+ dataClassification: ["customer_data", "pii"],
27
+ allowedFields: ["customer_id", "case_id", "plan"],
28
+ maxRecords: 10,
29
+ },
30
+ {
31
+ from: "provider_crm",
32
+ to: "external_email",
33
+ destinationType: "external_email",
34
+ dataClassification: ["customer_data", "pii"],
35
+ requiresApproval: true,
36
+ allowedDomains: ["customer.example"],
37
+ blockedFields: ["ssn", "access_token", "payment_method"],
38
+ },
39
+ ],
40
+ };
41
+
42
+ const gate = createMcpToolGate({
43
+ policy,
44
+ now: () => new Date("2026-06-11T12:00:00Z"),
45
+ idGenerator: demoIds(),
46
+ mappings: {
47
+ "provider.crm.search_customer": {
48
+ resource: (args) => `provider/customer/${String(args.customerId)}`,
49
+ dataFrom: "provider_crm",
50
+ dataTo: "agent_context",
51
+ dataClassification: ["customer_data", "pii"],
52
+ fieldSet: ["customer_id", "case_id", "plan"],
53
+ recordCount: 1,
54
+ },
55
+ "provider.billing.issue_credit": {
56
+ resource: (args) => `provider/customer/${String(args.customerId)}`,
57
+ amountUsd: (args) => Number(args.amountUsd),
58
+ idempotencyKey: (args) => String(args.idempotencyKey),
59
+ },
60
+ "provider.email.send_external": {
61
+ dataFrom: "provider_crm",
62
+ dataTo: "external_email",
63
+ destinationType: "external_email",
64
+ externalDomain: (args) => String(args.domain),
65
+ dataClassification: ["customer_data", "pii"],
66
+ fieldSet: (args) => (Array.isArray(args.fields) ? args.fields.map(String) : []),
67
+ recordCount: 1,
68
+ },
69
+ },
70
+ });
71
+
72
+ await run("1. MCP CRM read executes", {
73
+ params: {
74
+ name: "provider.crm.search_customer",
75
+ arguments: {
76
+ customerId: "cus_123",
77
+ },
78
+ },
79
+ });
80
+
81
+ await run("2. MCP credit requires approval", {
82
+ params: {
83
+ name: "provider.billing.issue_credit",
84
+ arguments: {
85
+ customerId: "cus_123",
86
+ amountUsd: 49,
87
+ idempotencyKey: "credit-case-1042-cus_123",
88
+ },
89
+ },
90
+ });
91
+
92
+ await run(
93
+ "3. Approved MCP credit executes",
94
+ {
95
+ params: {
96
+ name: "provider.billing.issue_credit",
97
+ arguments: {
98
+ customerId: "cus_123",
99
+ amountUsd: 49,
100
+ idempotencyKey: "credit-case-1042-cus_123",
101
+ },
102
+ },
103
+ },
104
+ "approval-credit-1",
105
+ );
106
+
107
+ await run("4. PII email requires approval", {
108
+ params: {
109
+ name: "provider.email.send_external",
110
+ arguments: {
111
+ domain: "alice.customer.example",
112
+ fields: ["customer_id", "case_id"],
113
+ },
114
+ },
115
+ });
116
+
117
+ async function run(
118
+ label: string,
119
+ request: { params: { name: string; arguments?: Record<string, unknown> } },
120
+ approvalId?: string,
121
+ ): Promise<void> {
122
+ const execution = await gate.run(
123
+ request,
124
+ {
125
+ agentId: "support-agent",
126
+ jobId: "case-1042",
127
+ userId: "user-17",
128
+ approvalId,
129
+ },
130
+ async ({ call, decision }) => ({
131
+ tool: call.name,
132
+ decisionId: decision.event.decisionId,
133
+ }),
134
+ );
135
+
136
+ console.log(
137
+ JSON.stringify(
138
+ {
139
+ label,
140
+ executed: execution.executed,
141
+ decision: execution.decision.type,
142
+ reasons: execution.decision.reasons,
143
+ challenge: execution.decision.challenge?.requiredApprovalFor,
144
+ result: execution.executed ? execution.result : undefined,
145
+ },
146
+ null,
147
+ 2,
148
+ ),
149
+ );
150
+ }
151
+
152
+ function demoIds(): () => string {
153
+ let next = 1;
154
+ return () => `mcp-demo-dec-${next++}`;
155
+ }
@@ -0,0 +1,137 @@
1
+ import policy from "./support-refund-policy.json" with { type: "json" };
2
+
3
+ import { createGuard, type GuardCheck } from "../src/index.ts";
4
+
5
+ const guard = createGuard({
6
+ policy,
7
+ now: () => new Date("2026-06-11T12:00:00Z"),
8
+ idGenerator: demoIds(),
9
+ });
10
+
11
+ const scenarios: Array<{ label: string; request: GuardCheck }> = [
12
+ {
13
+ label: "1. CRM PII read into agent context",
14
+ request: {
15
+ agentId: "support-agent",
16
+ userId: "user-17",
17
+ jobId: "case-pii",
18
+ tool: "crm.read_customer",
19
+ action: "read",
20
+ dataFrom: "provider_crm",
21
+ dataTo: "agent_context",
22
+ destinationType: "agent_context",
23
+ dataClassification: ["customer_data", "pii"],
24
+ fieldSet: ["customer_id", "case_id", "plan"],
25
+ recordCount: 1,
26
+ },
27
+ },
28
+ {
29
+ label: "2. Customer email needs approval",
30
+ request: {
31
+ agentId: "support-agent",
32
+ userId: "user-17",
33
+ jobId: "case-pii",
34
+ tool: "gmail.send",
35
+ action: "send",
36
+ dataFrom: "provider_crm",
37
+ dataTo: "external_email",
38
+ destinationType: "external_email",
39
+ externalDomain: "alice.customer.example",
40
+ dataClassification: ["customer_data", "pii"],
41
+ fieldSet: ["customer_id", "case_id"],
42
+ recordCount: 1,
43
+ },
44
+ },
45
+ {
46
+ label: "3. Unknown webhook destination is denied",
47
+ request: {
48
+ agentId: "support-agent",
49
+ userId: "user-17",
50
+ jobId: "case-pii",
51
+ tool: "webhook.post",
52
+ action: "send",
53
+ dataFrom: "provider_crm",
54
+ dataTo: "partner_webhook",
55
+ destinationType: "webhook",
56
+ externalDomain: "unknown.example",
57
+ dataClassification: ["customer_data", "pii"],
58
+ fieldSet: ["customer_id", "case_id"],
59
+ recordCount: 1,
60
+ approvalId: "approval-webhook-1",
61
+ },
62
+ },
63
+ {
64
+ label: "4. Raw PII prompt to model provider is denied",
65
+ request: {
66
+ agentId: "support-agent",
67
+ userId: "user-17",
68
+ jobId: "case-pii",
69
+ tool: "llm.prompt",
70
+ action: "send",
71
+ dataFrom: "provider_crm",
72
+ dataTo: "model_provider",
73
+ destinationType: "model_provider",
74
+ dataClassification: ["pii"],
75
+ fieldSet: ["customer_id", "full_date_of_birth"],
76
+ recordCount: 1,
77
+ approvalId: "approval-prompt-1",
78
+ },
79
+ },
80
+ {
81
+ label: "5. Bulk PII export exceeds record cap",
82
+ request: {
83
+ agentId: "support-agent",
84
+ userId: "user-17",
85
+ jobId: "case-pii",
86
+ tool: "file.export",
87
+ action: "export",
88
+ dataFrom: "provider_crm",
89
+ dataTo: "file_export",
90
+ destinationType: "file_export",
91
+ dataClassification: ["customer_data", "pii"],
92
+ fieldSet: ["customer_id", "case_id"],
93
+ recordCount: 51,
94
+ approvalId: "approval-export-1",
95
+ },
96
+ },
97
+ {
98
+ label: "6. Health record field is blocked for browser automation",
99
+ request: {
100
+ agentId: "support-agent",
101
+ userId: "user-17",
102
+ jobId: "case-pii",
103
+ tool: "browser.fill_form",
104
+ action: "send",
105
+ dataFrom: "provider_crm",
106
+ dataTo: "browser_form",
107
+ destinationType: "browser_form",
108
+ externalDomain: "forms.customer.example",
109
+ dataClassification: ["pii"],
110
+ fieldSet: ["customer_id", "health_record_id"],
111
+ recordCount: 1,
112
+ approvalId: "approval-browser-1",
113
+ },
114
+ },
115
+ ];
116
+
117
+ for (const scenario of scenarios) {
118
+ const decision = guard.check(scenario.request);
119
+ console.log(
120
+ JSON.stringify(
121
+ {
122
+ scenario: scenario.label,
123
+ decision: decision.type,
124
+ reasons: decision.reasons,
125
+ challenge: decision.challenge,
126
+ event: decision.event,
127
+ },
128
+ null,
129
+ 2,
130
+ ),
131
+ );
132
+ }
133
+
134
+ function demoIds(): () => string {
135
+ let next = 1;
136
+ return () => `pii-demo-dec-${next++}`;
137
+ }
@@ -0,0 +1,108 @@
1
+ import { createToolGate, type GuardCheck, type GuardPolicy } from "../src/index.ts";
2
+
3
+ const policy: GuardPolicy = {
4
+ tools: {
5
+ "web.search": {
6
+ action: "read",
7
+ },
8
+ "gmail.send": {
9
+ action: "send",
10
+ requiresApprovalIfPii: true,
11
+ allowedDomains: ["customer.example"],
12
+ blockedFields: ["ssn", "access_token", "payment_method"],
13
+ },
14
+ },
15
+ flows: [
16
+ {
17
+ from: "crm",
18
+ to: "external_email",
19
+ destinationType: "external_email",
20
+ dataClassification: ["pii"],
21
+ requiresApproval: true,
22
+ allowedDomains: ["customer.example"],
23
+ blockedFields: ["ssn", "access_token", "payment_method"],
24
+ },
25
+ ],
26
+ budgets: {
27
+ challengeAfterEstimatedCostUsdPerJob: 0.05,
28
+ maxEstimatedCostUsdPerJob: 0.1,
29
+ maxIdenticalToolCallsPerJob: 2,
30
+ maxToolCallsPerJob: 8,
31
+ },
32
+ defaultSensitiveDestinationDecision: "deny",
33
+ };
34
+
35
+ const gate = createToolGate({
36
+ policy,
37
+ now: () => new Date("2026-06-11T12:00:00Z"),
38
+ idGenerator: demoIds(),
39
+ });
40
+
41
+ await run("1. normal search executes", {
42
+ agentId: "research-agent",
43
+ jobId: "quickstart-job",
44
+ tool: "web.search",
45
+ action: "read",
46
+ resource: "query:agent tool guardrails",
47
+ callFingerprint: "web.search:agent-tool-guardrails",
48
+ estimatedCostUsd: 0.02,
49
+ });
50
+
51
+ await run("2. duplicate search executes once more", {
52
+ agentId: "research-agent",
53
+ jobId: "quickstart-job",
54
+ tool: "web.search",
55
+ action: "read",
56
+ resource: "query:agent tool guardrails",
57
+ callFingerprint: "web.search:agent-tool-guardrails",
58
+ estimatedCostUsd: 0.02,
59
+ });
60
+
61
+ await run("3. third identical search is denied", {
62
+ agentId: "research-agent",
63
+ jobId: "quickstart-job",
64
+ tool: "web.search",
65
+ action: "read",
66
+ resource: "query:agent tool guardrails",
67
+ callFingerprint: "web.search:agent-tool-guardrails",
68
+ estimatedCostUsd: 0.02,
69
+ });
70
+
71
+ await run("4. PII email pauses for approval", {
72
+ agentId: "support-agent",
73
+ jobId: "quickstart-pii",
74
+ tool: "gmail.send",
75
+ action: "send",
76
+ dataFrom: "crm",
77
+ dataTo: "external_email",
78
+ destinationType: "external_email",
79
+ externalDomain: "alice.customer.example",
80
+ dataClassification: ["pii"],
81
+ fieldSet: ["customer_id", "case_id"],
82
+ });
83
+
84
+ async function run(label: string, check: GuardCheck): Promise<void> {
85
+ const execution = await gate.run(check, async () => ({
86
+ ok: true,
87
+ tool: check.tool,
88
+ }));
89
+
90
+ console.log(
91
+ JSON.stringify(
92
+ {
93
+ label,
94
+ executed: execution.executed,
95
+ decision: execution.decision.type,
96
+ reasons: execution.decision.reasons,
97
+ challenge: execution.decision.challenge?.requiredApprovalFor,
98
+ },
99
+ null,
100
+ 2,
101
+ ),
102
+ );
103
+ }
104
+
105
+ function demoIds(): () => string {
106
+ let next = 1;
107
+ return () => `quickstart-dec-${next++}`;
108
+ }
@@ -0,0 +1,119 @@
1
+ import policy from "./support-refund-policy.json" with { type: "json" };
2
+
3
+ import { createGuard, type GuardCheck } from "../src/index.ts";
4
+
5
+ const guard = createGuard({
6
+ policy,
7
+ now: () => new Date("2026-06-11T12:00:00Z"),
8
+ idGenerator: demoIds(),
9
+ });
10
+
11
+ const steps: Array<{ label: string; request: GuardCheck }> = [
12
+ {
13
+ label: "1. Read customer context",
14
+ request: {
15
+ agentId: "support-agent",
16
+ userId: "user-17",
17
+ jobId: "case-1042",
18
+ tool: "crm.read_customer",
19
+ action: "read",
20
+ resource: "customer/cus_123",
21
+ dataFrom: "provider_crm",
22
+ dataTo: "agent_context",
23
+ dataClassification: ["customer_data"],
24
+ fieldSet: ["customer_id", "case_id", "plan"],
25
+ recordCount: 1,
26
+ estimatedTokens: 500,
27
+ estimatedCostUsd: 0.02,
28
+ },
29
+ },
30
+ {
31
+ label: "2. Agent proposes refund without approval",
32
+ request: {
33
+ agentId: "support-agent",
34
+ userId: "user-17",
35
+ jobId: "case-1042",
36
+ tool: "stripe.refund",
37
+ action: "pay",
38
+ resource: "payment/pi_123",
39
+ amountUsd: 49,
40
+ idempotencyKey: "refund-case-1042-pi_123",
41
+ estimatedTokens: 700,
42
+ estimatedCostUsd: 0.03,
43
+ },
44
+ },
45
+ {
46
+ label: "3. Refund executes after approval",
47
+ request: {
48
+ agentId: "support-agent",
49
+ userId: "user-17",
50
+ jobId: "case-1042",
51
+ tool: "stripe.refund",
52
+ action: "pay",
53
+ resource: "payment/pi_123",
54
+ amountUsd: 49,
55
+ idempotencyKey: "refund-case-1042-pi_123",
56
+ approvalId: "approval-refund-1",
57
+ estimatedTokens: 700,
58
+ estimatedCostUsd: 0.03,
59
+ },
60
+ },
61
+ {
62
+ label: "4. Agent retries same refund after timeout",
63
+ request: {
64
+ agentId: "support-agent",
65
+ userId: "user-17",
66
+ jobId: "case-1042",
67
+ tool: "stripe.refund",
68
+ action: "pay",
69
+ resource: "payment/pi_123",
70
+ amountUsd: 49,
71
+ idempotencyKey: "refund-case-1042-pi_123",
72
+ approvalId: "approval-refund-1",
73
+ estimatedTokens: 700,
74
+ estimatedCostUsd: 0.03,
75
+ },
76
+ },
77
+ {
78
+ label: "5. Agent tries to email PII externally",
79
+ request: {
80
+ agentId: "support-agent",
81
+ userId: "user-17",
82
+ jobId: "case-1042",
83
+ tool: "gmail.send",
84
+ action: "send",
85
+ dataFrom: "provider_crm",
86
+ dataTo: "external_email",
87
+ destinationType: "external_email",
88
+ externalDomain: "attacker.example",
89
+ dataClassification: ["customer_data", "pii"],
90
+ fieldSet: ["customer_id", "email", "payment_method"],
91
+ recordCount: 1,
92
+ approvalId: "approval-email-1",
93
+ estimatedTokens: 900,
94
+ estimatedCostUsd: 0.04,
95
+ },
96
+ },
97
+ ];
98
+
99
+ for (const step of steps) {
100
+ const decision = guard.check(step.request);
101
+ console.log(
102
+ JSON.stringify(
103
+ {
104
+ step: step.label,
105
+ decision: decision.type,
106
+ reasons: decision.reasons,
107
+ challenge: decision.challenge,
108
+ event: decision.event,
109
+ },
110
+ null,
111
+ 2,
112
+ ),
113
+ );
114
+ }
115
+
116
+ function demoIds(): () => string {
117
+ let next = 1;
118
+ return () => `demo-dec-${next++}`;
119
+ }
@@ -0,0 +1,110 @@
1
+ {
2
+ "tools": {
3
+ "stripe.refund": {
4
+ "action": "pay",
5
+ "requiresApproval": true,
6
+ "maxAmountUsd": 100,
7
+ "requireIdempotencyKey": true,
8
+ "singleUse": true
9
+ },
10
+ "gmail.send": {
11
+ "action": "send",
12
+ "requiresApprovalIfPii": true,
13
+ "allowedDomains": ["customer.example"],
14
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
15
+ },
16
+ "webhook.post": {
17
+ "action": "send",
18
+ "requiresApprovalIfPii": true,
19
+ "allowedDomains": ["approved.partner.example"],
20
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
21
+ },
22
+ "llm.prompt": {
23
+ "action": "send",
24
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
25
+ },
26
+ "file.export": {
27
+ "action": "export",
28
+ "requiresApprovalIfPii": true,
29
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
30
+ },
31
+ "browser.fill_form": {
32
+ "action": "send",
33
+ "requiresApprovalIfPii": true,
34
+ "allowedDomains": ["customer.example"],
35
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
36
+ },
37
+ "crm.read_customer": {
38
+ "action": "read"
39
+ },
40
+ "crm.update_customer": {
41
+ "action": "write",
42
+ "requiresApproval": true,
43
+ "allowedFields": ["case_id", "plan", "renewal_date", "support_note"]
44
+ }
45
+ },
46
+ "flows": [
47
+ {
48
+ "from": "provider_crm",
49
+ "to": "agent_context",
50
+ "dataClassification": ["customer_data", "pii"],
51
+ "allowedFields": ["customer_id", "case_id", "plan", "renewal_date"],
52
+ "maxRecords": 10
53
+ },
54
+ {
55
+ "from": "provider_crm",
56
+ "to": "external_email",
57
+ "destinationType": "external_email",
58
+ "dataClassification": ["customer_data", "pii"],
59
+ "requiresApproval": true,
60
+ "allowedDomains": ["customer.example"],
61
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
62
+ },
63
+ {
64
+ "from": "provider_crm",
65
+ "to": "partner_webhook",
66
+ "destinationType": "webhook",
67
+ "dataClassification": ["customer_data", "pii"],
68
+ "requiresApproval": true,
69
+ "allowedDomains": ["approved.partner.example"],
70
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
71
+ },
72
+ {
73
+ "from": "provider_crm",
74
+ "to": "browser_form",
75
+ "destinationType": "browser_form",
76
+ "dataClassification": ["customer_data", "pii"],
77
+ "requiresApproval": true,
78
+ "allowedDomains": ["customer.example"],
79
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
80
+ },
81
+ {
82
+ "from": "provider_crm",
83
+ "to": "file_export",
84
+ "destinationType": "file_export",
85
+ "dataClassification": ["customer_data", "pii"],
86
+ "requiresApproval": true,
87
+ "maxRecords": 50,
88
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
89
+ },
90
+ {
91
+ "from": "provider_crm",
92
+ "to": "model_provider",
93
+ "destinationType": "model_provider",
94
+ "dataClassification": ["pii"],
95
+ "decision": "deny",
96
+ "blockedFields": ["ssn", "access_token", "payment_method", "full_date_of_birth", "health_record_id"]
97
+ },
98
+ {
99
+ "from": "secrets_manager",
100
+ "to": "model_provider",
101
+ "decision": "deny"
102
+ }
103
+ ],
104
+ "budgets": {
105
+ "maxToolCallsPerJob": 5,
106
+ "maxRetriesPerTool": 1,
107
+ "maxTokensPerJob": 10000,
108
+ "maxEstimatedCostUsdPerJob": 1
109
+ }
110
+ }