ai-agent-guardrails 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,358 @@
1
+ // src/utils.ts
2
+ function cryptoRandomId() {
3
+ if (typeof globalThis.crypto?.randomUUID === "function") {
4
+ return globalThis.crypto.randomUUID();
5
+ }
6
+ return `req_${Math.random().toString(16).slice(2)}`;
7
+ }
8
+ function createDefaultContext(requestId) {
9
+ return {
10
+ requestId: requestId ?? cryptoRandomId(),
11
+ toolCalls: 0,
12
+ maxToolCalls: 8,
13
+ startTime: Date.now(),
14
+ maxDurationMs: 6e4
15
+ // 60 seconds default
16
+ };
17
+ }
18
+ function withTimeout(promise, ms) {
19
+ return new Promise((resolve, reject) => {
20
+ const timeoutId = setTimeout(() => {
21
+ reject(new Error(`Operation timed out after ${ms}ms`));
22
+ }, ms);
23
+ promise.then((value) => {
24
+ clearTimeout(timeoutId);
25
+ resolve(value);
26
+ }).catch((error) => {
27
+ clearTimeout(timeoutId);
28
+ reject(error);
29
+ });
30
+ });
31
+ }
32
+
33
+ // src/guard-tools.ts
34
+ function guardTools(tools, opts) {
35
+ const ctx = opts.ctx ?? createDefaultContext();
36
+ const audit = opts.audit;
37
+ const timeoutMs = opts.timeoutMs ?? 15e3;
38
+ const redactor = opts.redactor;
39
+ const wrapped = {};
40
+ for (const [toolName, tool] of Object.entries(tools)) {
41
+ const originalExecute = tool.execute;
42
+ const needsApproval = tool.needsApproval ?? (async (input) => {
43
+ try {
44
+ const { risk, reason } = await opts.policy.classify(toolName, input);
45
+ const decision = await opts.policy.decide({ toolName, input, ctx, risk, reason });
46
+ return Boolean(decision.needsApproval);
47
+ } catch (error) {
48
+ console.error(`[guardTools] Error in needsApproval check for ${toolName}:`, error);
49
+ return true;
50
+ }
51
+ });
52
+ wrapped[toolName] = {
53
+ ...tool,
54
+ needsApproval,
55
+ execute: originalExecute ? async (input) => {
56
+ const timestamp = Date.now();
57
+ const redactedInput = redactor ? redactor.redact(input) : input;
58
+ audit?.emit({
59
+ type: "tool_call_attempted",
60
+ toolName,
61
+ input: redactedInput,
62
+ requestId: ctx.requestId,
63
+ timestamp
64
+ });
65
+ ctx.toolCalls += 1;
66
+ if (ctx.toolCalls > ctx.maxToolCalls) {
67
+ const reason2 = `Tool budget exceeded (maxToolCalls=${ctx.maxToolCalls})`;
68
+ audit?.emit({
69
+ type: "budget_exceeded",
70
+ reason: reason2,
71
+ requestId: ctx.requestId,
72
+ timestamp: Date.now()
73
+ });
74
+ throw new Error(reason2);
75
+ }
76
+ if (ctx.maxDurationMs) {
77
+ const elapsed = Date.now() - ctx.startTime;
78
+ if (elapsed > ctx.maxDurationMs) {
79
+ const reason2 = `Time budget exceeded (maxDurationMs=${ctx.maxDurationMs})`;
80
+ audit?.emit({
81
+ type: "budget_exceeded",
82
+ reason: reason2,
83
+ requestId: ctx.requestId,
84
+ timestamp: Date.now()
85
+ });
86
+ throw new Error(reason2);
87
+ }
88
+ }
89
+ const { risk, reason } = await opts.policy.classify(toolName, input);
90
+ const decision = await opts.policy.decide({ toolName, input, ctx, risk, reason });
91
+ if (!decision.allow) {
92
+ audit?.emit({
93
+ type: "tool_call_blocked",
94
+ toolName,
95
+ reason: decision.reason,
96
+ requestId: ctx.requestId,
97
+ timestamp: Date.now()
98
+ });
99
+ throw new Error(`Tool call blocked: ${decision.reason}`);
100
+ }
101
+ if (decision.needsApproval) {
102
+ audit?.emit({
103
+ type: "tool_call_needs_approval",
104
+ toolName,
105
+ reason: decision.reason ?? "approval required",
106
+ requestId: ctx.requestId,
107
+ timestamp: Date.now()
108
+ });
109
+ }
110
+ const t0 = Date.now();
111
+ try {
112
+ const result = await withTimeout(originalExecute(input), timeoutMs);
113
+ const redactedResult = redactor ? redactor.redact(result) : result;
114
+ audit?.emit({
115
+ type: "tool_call_executed",
116
+ toolName,
117
+ durationMs: Date.now() - t0,
118
+ requestId: ctx.requestId,
119
+ timestamp: Date.now()
120
+ });
121
+ return result;
122
+ } catch (error) {
123
+ if (error.message?.includes("timed out")) {
124
+ audit?.emit({
125
+ type: "tool_call_timeout",
126
+ toolName,
127
+ timeoutMs,
128
+ requestId: ctx.requestId,
129
+ timestamp: Date.now()
130
+ });
131
+ }
132
+ throw error;
133
+ }
134
+ } : void 0
135
+ };
136
+ }
137
+ return wrapped;
138
+ }
139
+
140
+ // src/policy.ts
141
+ function createSimplePolicy(options) {
142
+ const { allowlist, denylist, requireApprovalForRisk = ["write", "admin"] } = options;
143
+ return {
144
+ classify(toolName) {
145
+ const lowered = toolName.toLowerCase();
146
+ if (lowered.includes("delete") || lowered.includes("remove") || lowered.includes("destroy") || lowered.includes("drop")) {
147
+ return { risk: "admin", reason: "destructive operation" };
148
+ }
149
+ if (lowered.includes("create") || lowered.includes("write") || lowered.includes("update") || lowered.includes("modify") || lowered.includes("insert") || lowered.includes("send") || lowered.includes("post")) {
150
+ return { risk: "write", reason: "write operation" };
151
+ }
152
+ return { risk: "read", reason: "read-only operation" };
153
+ },
154
+ decide({ toolName, risk, ctx }) {
155
+ if (denylist && denylist.includes(toolName)) {
156
+ return { allow: false, reason: `Tool '${toolName}' is in denylist` };
157
+ }
158
+ if (allowlist && !allowlist.includes(toolName)) {
159
+ return { allow: false, reason: `Tool '${toolName}' is not in allowlist` };
160
+ }
161
+ if (requireApprovalForRisk.includes(risk)) {
162
+ return {
163
+ allow: true,
164
+ needsApproval: true,
165
+ reason: `${risk} operation requires approval`
166
+ };
167
+ }
168
+ return { allow: true };
169
+ }
170
+ };
171
+ }
172
+ var PolicyBuilder = class {
173
+ classifiers = [];
174
+ rules = [];
175
+ /**
176
+ * Add a classifier function
177
+ */
178
+ addClassifier(classifier) {
179
+ this.classifiers.push(classifier);
180
+ return this;
181
+ }
182
+ /**
183
+ * Add a decision rule
184
+ */
185
+ addRule(rule) {
186
+ this.rules.push(rule);
187
+ return this;
188
+ }
189
+ /**
190
+ * Build the final policy
191
+ */
192
+ build() {
193
+ return {
194
+ classify: (toolName, input) => {
195
+ for (const classifier of this.classifiers) {
196
+ const result = classifier(toolName, input);
197
+ if (result) return result;
198
+ }
199
+ return { risk: "read" };
200
+ },
201
+ decide: (args) => {
202
+ for (const rule of this.rules) {
203
+ const decision = rule(args);
204
+ if (decision) return decision;
205
+ }
206
+ return { allow: true };
207
+ }
208
+ };
209
+ }
210
+ };
211
+
212
+ // src/audit.ts
213
+ var InMemoryAuditSink = class {
214
+ events = [];
215
+ emit(event) {
216
+ this.events.push(event);
217
+ }
218
+ /**
219
+ * Get all stored events
220
+ */
221
+ getEvents() {
222
+ return this.events;
223
+ }
224
+ /**
225
+ * Clear all stored events
226
+ */
227
+ clear() {
228
+ this.events = [];
229
+ }
230
+ /**
231
+ * Get events for a specific request
232
+ */
233
+ getEventsForRequest(requestId) {
234
+ return this.events.filter((e) => e.requestId === requestId);
235
+ }
236
+ };
237
+ var ConsoleAuditSink = class {
238
+ prefix;
239
+ constructor(prefix = "[audit]") {
240
+ this.prefix = prefix;
241
+ }
242
+ emit(event) {
243
+ console.log(this.prefix, JSON.stringify(event, null, 2));
244
+ }
245
+ };
246
+ var FileAuditSink = class {
247
+ writeStream;
248
+ constructor(filePath) {
249
+ import('fs').then((fs) => {
250
+ this.writeStream = fs.createWriteStream(filePath, { flags: "a" });
251
+ });
252
+ }
253
+ emit(event) {
254
+ if (this.writeStream) {
255
+ this.writeStream.write(JSON.stringify(event) + "\n");
256
+ }
257
+ }
258
+ /**
259
+ * Close the file stream
260
+ */
261
+ close() {
262
+ this.writeStream?.end();
263
+ }
264
+ };
265
+
266
+ // src/redaction.ts
267
+ var DEFAULT_SECRET_PATTERNS = [
268
+ /\b(sk-[a-zA-Z0-9]{48})\b/g,
269
+ // OpenAI API keys
270
+ /\b(sk_live_[a-zA-Z0-9]{24,})\b/g,
271
+ // Stripe live keys
272
+ /\b(sk_test_[a-zA-Z0-9]{24,})\b/g,
273
+ // Stripe test keys
274
+ /\b([a-zA-Z0-9_-]{40})\b/g,
275
+ // GitHub tokens (40 chars)
276
+ /\b(ghp_[a-zA-Z0-9]{36})\b/g,
277
+ // GitHub personal access tokens
278
+ /\b(gho_[a-zA-Z0-9]{36})\b/g,
279
+ // GitHub OAuth tokens
280
+ /\b(AKIA[0-9A-Z]{16})\b/g,
281
+ // AWS access keys
282
+ /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/g,
283
+ // Private keys
284
+ /\b([a-zA-Z0-9_-]{32,})\b/g
285
+ // Generic long tokens
286
+ ];
287
+ var DEFAULT_PII_PATTERNS = [
288
+ /\b\d{3}-\d{2}-\d{4}\b/g,
289
+ // SSN
290
+ /\b\d{16}\b/g,
291
+ // Credit card numbers
292
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g
293
+ // Email addresses
294
+ ];
295
+ function createRegexRedactor(patterns, replacement = "[REDACTED]") {
296
+ return {
297
+ redact(value) {
298
+ if (typeof value === "string") {
299
+ let redacted = value;
300
+ for (const pattern of patterns) {
301
+ redacted = redacted.replace(pattern, replacement);
302
+ }
303
+ return redacted;
304
+ }
305
+ if (Array.isArray(value)) {
306
+ return value.map((item) => this.redact(item));
307
+ }
308
+ if (value && typeof value === "object") {
309
+ const result = {};
310
+ for (const [key, val] of Object.entries(value)) {
311
+ result[key] = this.redact(val);
312
+ }
313
+ return result;
314
+ }
315
+ return value;
316
+ }
317
+ };
318
+ }
319
+ function createDefaultRedactor() {
320
+ return createRegexRedactor([...DEFAULT_SECRET_PATTERNS, ...DEFAULT_PII_PATTERNS]);
321
+ }
322
+ function createFieldRedactor(fieldsToRedact, replacement = "[REDACTED]") {
323
+ const fieldSet = new Set(fieldsToRedact.map((f) => f.toLowerCase()));
324
+ return {
325
+ redact(value) {
326
+ if (Array.isArray(value)) {
327
+ return value.map((item) => this.redact(item));
328
+ }
329
+ if (value && typeof value === "object") {
330
+ const result = {};
331
+ for (const [key, val] of Object.entries(value)) {
332
+ if (fieldSet.has(key.toLowerCase())) {
333
+ result[key] = replacement;
334
+ } else {
335
+ result[key] = this.redact(val);
336
+ }
337
+ }
338
+ return result;
339
+ }
340
+ return value;
341
+ }
342
+ };
343
+ }
344
+ function composeRedactors(...redactors) {
345
+ return {
346
+ redact(value) {
347
+ let result = value;
348
+ for (const redactor of redactors) {
349
+ result = redactor.redact(result);
350
+ }
351
+ return result;
352
+ }
353
+ };
354
+ }
355
+
356
+ export { ConsoleAuditSink, FileAuditSink, InMemoryAuditSink, PolicyBuilder, composeRedactors, createDefaultContext, createDefaultRedactor, createFieldRedactor, createRegexRedactor, createSimplePolicy, guardTools, withTimeout };
357
+ //# sourceMappingURL=index.js.map
358
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils.ts","../src/guard-tools.ts","../src/policy.ts","../src/audit.ts","../src/redaction.ts"],"names":["reason"],"mappings":"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","file":"index.js","sourcesContent":["import type { GuardContext } from './types.js';\n\n/**\n * Generate a unique request ID\n */\nfunction cryptoRandomId(): string {\n // Use crypto.randomUUID() in Node 20+ and modern browsers\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID\n return `req_${Math.random().toString(16).slice(2)}`;\n}\n\n/**\n * Create a default guard context with budget limits\n */\nexport function createDefaultContext(requestId?: string): GuardContext {\n return {\n requestId: requestId ?? cryptoRandomId(),\n toolCalls: 0,\n maxToolCalls: 8,\n startTime: Date.now(),\n maxDurationMs: 60_000, // 60 seconds default\n };\n}\n\n/**\n * Wrap a promise with a timeout\n */\nexport function withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`Operation timed out after ${ms}ms`));\n }, ms);\n\n promise\n .then(value => {\n clearTimeout(timeoutId);\n resolve(value);\n })\n .catch(error => {\n clearTimeout(timeoutId);\n reject(error);\n });\n });\n}\n","import type { GuardToolsOptions, ToolLike, GuardContext } from './types.js';\nimport { createDefaultContext, withTimeout } from './utils.js';\n\n/**\n * Wrap a toolset with guardrails enforcement\n *\n * This is the main entry point for the guardrails package. It wraps AI SDK tools\n * with policy enforcement, budget checks, timeouts, and audit logging.\n *\n * @example\n * ```ts\n * const tools = guardTools(mcpTools, {\n * policy: createSimplePolicy({ requireApprovalForRisk: ['write', 'admin'] }),\n * audit: new ConsoleAuditSink(),\n * timeoutMs: 10_000,\n * });\n * ```\n */\nexport function guardTools<T extends Record<string, ToolLike>>(\n tools: T,\n opts: GuardToolsOptions\n): T {\n const ctx = opts.ctx ?? createDefaultContext();\n const audit = opts.audit;\n const timeoutMs = opts.timeoutMs ?? 15_000;\n const redactor = opts.redactor;\n\n const wrapped: Record<string, ToolLike> = {};\n\n for (const [toolName, tool] of Object.entries(tools)) {\n const originalExecute = tool.execute;\n\n // Set needsApproval based on policy decision\n const needsApproval =\n tool.needsApproval ??\n (async (input: unknown) => {\n try {\n const { risk, reason } = await opts.policy.classify(toolName, input);\n const decision = await opts.policy.decide({ toolName, input, ctx, risk, reason });\n\n // Return true if decision requires approval\n return Boolean((decision as any).needsApproval);\n } catch (error) {\n console.error(`[guardTools] Error in needsApproval check for ${toolName}:`, error);\n // Fail closed: require approval on error\n return true;\n }\n });\n\n wrapped[toolName] = {\n ...tool,\n needsApproval,\n execute: originalExecute\n ? async (input: any) => {\n const timestamp = Date.now();\n\n // Redact input before logging if redactor is provided\n const redactedInput = redactor ? redactor.redact(input) : input;\n\n audit?.emit({\n type: 'tool_call_attempted',\n toolName,\n input: redactedInput,\n requestId: ctx.requestId,\n timestamp,\n });\n\n // Check tool call budget\n ctx.toolCalls += 1;\n if (ctx.toolCalls > ctx.maxToolCalls) {\n const reason = `Tool budget exceeded (maxToolCalls=${ctx.maxToolCalls})`;\n audit?.emit({\n type: 'budget_exceeded',\n reason,\n requestId: ctx.requestId,\n timestamp: Date.now(),\n });\n throw new Error(reason);\n }\n\n // Check elapsed time budget\n if (ctx.maxDurationMs) {\n const elapsed = Date.now() - ctx.startTime;\n if (elapsed > ctx.maxDurationMs) {\n const reason = `Time budget exceeded (maxDurationMs=${ctx.maxDurationMs})`;\n audit?.emit({\n type: 'budget_exceeded',\n reason,\n requestId: ctx.requestId,\n timestamp: Date.now(),\n });\n throw new Error(reason);\n }\n }\n\n // Classify and decide\n const { risk, reason } = await opts.policy.classify(toolName, input);\n const decision = await opts.policy.decide({ toolName, input, ctx, risk, reason });\n\n // Block if not allowed\n if (!decision.allow) {\n audit?.emit({\n type: 'tool_call_blocked',\n toolName,\n reason: decision.reason,\n requestId: ctx.requestId,\n timestamp: Date.now(),\n });\n throw new Error(`Tool call blocked: ${decision.reason}`);\n }\n\n // Log if approval is needed (AI SDK will handle the actual approval flow)\n if ((decision as any).needsApproval) {\n audit?.emit({\n type: 'tool_call_needs_approval',\n toolName,\n reason: (decision as any).reason ?? 'approval required',\n requestId: ctx.requestId,\n timestamp: Date.now(),\n });\n }\n\n // Execute with timeout\n const t0 = Date.now();\n try {\n const result = await withTimeout(originalExecute(input), timeoutMs);\n \n // Redact output if redactor is provided\n const redactedResult = redactor ? redactor.redact(result) : result;\n \n audit?.emit({\n type: 'tool_call_executed',\n toolName,\n durationMs: Date.now() - t0,\n requestId: ctx.requestId,\n timestamp: Date.now(),\n });\n \n return result; // Return original result, not redacted (redaction is for logging only)\n } catch (error: any) {\n if (error.message?.includes('timed out')) {\n audit?.emit({\n type: 'tool_call_timeout',\n toolName,\n timeoutMs,\n requestId: ctx.requestId,\n timestamp: Date.now(),\n });\n }\n throw error;\n }\n }\n : undefined,\n };\n }\n\n return wrapped as T;\n}\n","import type { Risk, GuardPolicy, GuardDecision, GuardContext } from './types.js';\n\n/**\n * Create a simple policy with allowlist/denylist\n */\nexport function createSimplePolicy(options: {\n allowlist?: string[];\n denylist?: string[];\n requireApprovalForRisk?: Risk[];\n}): GuardPolicy {\n const { allowlist, denylist, requireApprovalForRisk = ['write', 'admin'] } = options;\n\n return {\n classify(toolName: string) {\n // Classify based on tool name patterns\n const lowered = toolName.toLowerCase();\n\n if (\n lowered.includes('delete') ||\n lowered.includes('remove') ||\n lowered.includes('destroy') ||\n lowered.includes('drop')\n ) {\n return { risk: 'admin', reason: 'destructive operation' };\n }\n\n if (\n lowered.includes('create') ||\n lowered.includes('write') ||\n lowered.includes('update') ||\n lowered.includes('modify') ||\n lowered.includes('insert') ||\n lowered.includes('send') ||\n lowered.includes('post')\n ) {\n return { risk: 'write', reason: 'write operation' };\n }\n\n return { risk: 'read', reason: 'read-only operation' };\n },\n\n decide({ toolName, risk, ctx }): GuardDecision {\n // Check denylist first\n if (denylist && denylist.includes(toolName)) {\n return { allow: false, reason: `Tool '${toolName}' is in denylist` };\n }\n\n // Check allowlist if provided\n if (allowlist && !allowlist.includes(toolName)) {\n return { allow: false, reason: `Tool '${toolName}' is not in allowlist` };\n }\n\n // Check if approval is required for this risk level\n if (requireApprovalForRisk.includes(risk)) {\n return {\n allow: true,\n needsApproval: true,\n reason: `${risk} operation requires approval`,\n };\n }\n\n return { allow: true };\n },\n };\n}\n\n/**\n * Create a composable policy builder\n */\nexport class PolicyBuilder {\n private classifiers: Array<(toolName: string, input: unknown) => { risk: Risk; reason?: string } | null> = [];\n private rules: Array<\n (args: {\n toolName: string;\n input: unknown;\n ctx: GuardContext;\n risk: Risk;\n reason?: string;\n }) => GuardDecision | null\n > = [];\n\n /**\n * Add a classifier function\n */\n addClassifier(\n classifier: (toolName: string, input: unknown) => { risk: Risk; reason?: string } | null\n ): this {\n this.classifiers.push(classifier);\n return this;\n }\n\n /**\n * Add a decision rule\n */\n addRule(\n rule: (args: {\n toolName: string;\n input: unknown;\n ctx: GuardContext;\n risk: Risk;\n reason?: string;\n }) => GuardDecision | null\n ): this {\n this.rules.push(rule);\n return this;\n }\n\n /**\n * Build the final policy\n */\n build(): GuardPolicy {\n return {\n classify: (toolName: string, input: unknown) => {\n // Try each classifier in order\n for (const classifier of this.classifiers) {\n const result = classifier(toolName, input);\n if (result) return result;\n }\n // Default to read if no classifier matches\n return { risk: 'read' };\n },\n\n decide: args => {\n // Try each rule in order\n for (const rule of this.rules) {\n const decision = rule(args);\n if (decision) return decision;\n }\n // Default to allow if no rule blocks\n return { allow: true };\n },\n };\n }\n}\n","import type { AuditEvent, AuditSink } from './types.js';\n\n/**\n * In-memory audit sink that stores events in an array\n */\nexport class InMemoryAuditSink implements AuditSink {\n private events: AuditEvent[] = [];\n\n emit(event: AuditEvent): void {\n this.events.push(event);\n }\n\n /**\n * Get all stored events\n */\n getEvents(): ReadonlyArray<AuditEvent> {\n return this.events;\n }\n\n /**\n * Clear all stored events\n */\n clear(): void {\n this.events = [];\n }\n\n /**\n * Get events for a specific request\n */\n getEventsForRequest(requestId: string): AuditEvent[] {\n return this.events.filter(e => e.requestId === requestId);\n }\n}\n\n/**\n * Console audit sink that logs events to console\n */\nexport class ConsoleAuditSink implements AuditSink {\n private prefix: string;\n\n constructor(prefix = '[audit]') {\n this.prefix = prefix;\n }\n\n emit(event: AuditEvent): void {\n console.log(this.prefix, JSON.stringify(event, null, 2));\n }\n}\n\n/**\n * File audit sink that writes events to a JSONL file\n * Note: This is for Node.js environments only\n */\nexport class FileAuditSink implements AuditSink {\n private writeStream: any;\n\n constructor(filePath: string) {\n // Dynamic import for Node.js fs\n import('node:fs').then(fs => {\n this.writeStream = fs.createWriteStream(filePath, { flags: 'a' });\n });\n }\n\n emit(event: AuditEvent): void {\n if (this.writeStream) {\n this.writeStream.write(JSON.stringify(event) + '\\n');\n }\n }\n\n /**\n * Close the file stream\n */\n close(): void {\n this.writeStream?.end();\n }\n}\n","import type { Redactor } from './types.js';\n\n/**\n * Default patterns for secret detection\n */\nconst DEFAULT_SECRET_PATTERNS = [\n /\\b(sk-[a-zA-Z0-9]{48})\\b/g, // OpenAI API keys\n /\\b(sk_live_[a-zA-Z0-9]{24,})\\b/g, // Stripe live keys\n /\\b(sk_test_[a-zA-Z0-9]{24,})\\b/g, // Stripe test keys\n /\\b([a-zA-Z0-9_-]{40})\\b/g, // GitHub tokens (40 chars)\n /\\b(ghp_[a-zA-Z0-9]{36})\\b/g, // GitHub personal access tokens\n /\\b(gho_[a-zA-Z0-9]{36})\\b/g, // GitHub OAuth tokens\n /\\b(AKIA[0-9A-Z]{16})\\b/g, // AWS access keys\n /-----BEGIN\\s+(?:RSA\\s+)?PRIVATE\\s+KEY-----/g, // Private keys\n /\\b([a-zA-Z0-9_-]{32,})\\b/g, // Generic long tokens\n];\n\n/**\n * Default patterns for PII detection\n */\nconst DEFAULT_PII_PATTERNS = [\n /\\b\\d{3}-\\d{2}-\\d{4}\\b/g, // SSN\n /\\b\\d{16}\\b/g, // Credit card numbers\n /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b/g, // Email addresses\n];\n\n/**\n * Create a regex-based redactor\n */\nexport function createRegexRedactor(patterns: RegExp[], replacement = '[REDACTED]'): Redactor {\n return {\n redact(value: unknown): unknown {\n if (typeof value === 'string') {\n let redacted = value;\n for (const pattern of patterns) {\n redacted = redacted.replace(pattern, replacement);\n }\n return redacted;\n }\n\n if (Array.isArray(value)) {\n return value.map(item => this.redact(item));\n }\n\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value)) {\n result[key] = this.redact(val);\n }\n return result;\n }\n\n return value;\n },\n };\n}\n\n/**\n * Create a default redactor with common secret and PII patterns\n */\nexport function createDefaultRedactor(): Redactor {\n return createRegexRedactor([...DEFAULT_SECRET_PATTERNS, ...DEFAULT_PII_PATTERNS]);\n}\n\n/**\n * Field-based redactor that redacts specific fields\n */\nexport function createFieldRedactor(fieldsToRedact: string[], replacement = '[REDACTED]'): Redactor {\n const fieldSet = new Set(fieldsToRedact.map(f => f.toLowerCase()));\n\n return {\n redact(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(item => this.redact(item));\n }\n\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value)) {\n if (fieldSet.has(key.toLowerCase())) {\n result[key] = replacement;\n } else {\n result[key] = this.redact(val);\n }\n }\n return result;\n }\n\n return value;\n },\n };\n}\n\n/**\n * Composite redactor that chains multiple redactors\n */\nexport function composeRedactors(...redactors: Redactor[]): Redactor {\n return {\n redact(value: unknown): unknown {\n let result = value;\n for (const redactor of redactors) {\n result = redactor.redact(result);\n }\n return result;\n },\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "ai-agent-guardrails",
3
+ "version": "0.0.1",
4
+ "description": "Production-grade tool firewall for AI SDK agents with approval gates, budgets, and audit logging",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "test": "echo \"Error: no test specified\" && exit 1"
18
+ },
19
+ "keywords": [
20
+ "ai",
21
+ "sdk",
22
+ "guardrails",
23
+ "mcp",
24
+ "security",
25
+ "middleware",
26
+ "agents",
27
+ "vercel",
28
+ "ai-sdk",
29
+ "tool-calling",
30
+ "approval",
31
+ "audit",
32
+ "redaction"
33
+ ],
34
+ "author": "Krish Gupta",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/KrxGu/ai-agent-guardrails.git",
39
+ "directory": "packages/ai-agent-guardrails"
40
+ },
41
+ "homepage": "https://github.com/KrxGu/ai-agent-guardrails#readme",
42
+ "bugs": {
43
+ "url": "https://github.com/KrxGu/ai-agent-guardrails/issues"
44
+ },
45
+ "packageManager": "pnpm@10.27.0",
46
+ "dependencies": {
47
+ "ai": "^6.0.6",
48
+ "zod": "^4.3.5"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.0.3",
52
+ "tsup": "^8.5.1",
53
+ "typescript": "^5.9.3"
54
+ }
55
+ }
package/src/audit.ts ADDED
@@ -0,0 +1,76 @@
1
+ import type { AuditEvent, AuditSink } from './types.js';
2
+
3
+ /**
4
+ * In-memory audit sink that stores events in an array
5
+ */
6
+ export class InMemoryAuditSink implements AuditSink {
7
+ private events: AuditEvent[] = [];
8
+
9
+ emit(event: AuditEvent): void {
10
+ this.events.push(event);
11
+ }
12
+
13
+ /**
14
+ * Get all stored events
15
+ */
16
+ getEvents(): ReadonlyArray<AuditEvent> {
17
+ return this.events;
18
+ }
19
+
20
+ /**
21
+ * Clear all stored events
22
+ */
23
+ clear(): void {
24
+ this.events = [];
25
+ }
26
+
27
+ /**
28
+ * Get events for a specific request
29
+ */
30
+ getEventsForRequest(requestId: string): AuditEvent[] {
31
+ return this.events.filter(e => e.requestId === requestId);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Console audit sink that logs events to console
37
+ */
38
+ export class ConsoleAuditSink implements AuditSink {
39
+ private prefix: string;
40
+
41
+ constructor(prefix = '[audit]') {
42
+ this.prefix = prefix;
43
+ }
44
+
45
+ emit(event: AuditEvent): void {
46
+ console.log(this.prefix, JSON.stringify(event, null, 2));
47
+ }
48
+ }
49
+
50
+ /**
51
+ * File audit sink that writes events to a JSONL file
52
+ * Note: This is for Node.js environments only
53
+ */
54
+ export class FileAuditSink implements AuditSink {
55
+ private writeStream: any;
56
+
57
+ constructor(filePath: string) {
58
+ // Dynamic import for Node.js fs
59
+ import('node:fs').then(fs => {
60
+ this.writeStream = fs.createWriteStream(filePath, { flags: 'a' });
61
+ });
62
+ }
63
+
64
+ emit(event: AuditEvent): void {
65
+ if (this.writeStream) {
66
+ this.writeStream.write(JSON.stringify(event) + '\n');
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Close the file stream
72
+ */
73
+ close(): void {
74
+ this.writeStream?.end();
75
+ }
76
+ }