@dudousxd/nestjs-agent-core 0.15.5 → 0.17.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,2445 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/guardrails/types.ts
5
+ var GUARDRAIL_STAGES = [
6
+ "llm_request",
7
+ "llm_response",
8
+ "tool_args",
9
+ "tool_result",
10
+ "tool_description"
11
+ ];
12
+ var ACTION_SEVERITY = {
13
+ allow: 0,
14
+ log: 1,
15
+ redact: 2,
16
+ approve: 3,
17
+ block: 4
18
+ };
19
+ var PII_TYPES = [
20
+ "email",
21
+ "phone",
22
+ "cpf",
23
+ "cnpj",
24
+ "ssn",
25
+ "credit_card",
26
+ "iban",
27
+ "ip_address"
28
+ ];
29
+ var SECRET_TYPES = [
30
+ "openai_key",
31
+ "anthropic_key",
32
+ "aws_access_key",
33
+ "aws_secret_key",
34
+ "github_token",
35
+ "gitlab_token",
36
+ "slack_token",
37
+ "google_api_key",
38
+ "stripe_key",
39
+ "jwt",
40
+ "bearer_token",
41
+ "private_key",
42
+ "generic_secret"
43
+ ];
44
+
45
+ // src/guardrails/detectors/pii.ts
46
+ function isIPv4(raw) {
47
+ const parts = raw.split(".");
48
+ return parts.length === 4 && parts.every((p) => /^(?:0|[1-9]\d{0,2})$/.test(p) && Number(p) <= 255);
49
+ }
50
+ __name(isIPv4, "isIPv4");
51
+ function luhnValid(digits) {
52
+ if (!/^\d+$/.test(digits)) return false;
53
+ let sum = 0;
54
+ let double = false;
55
+ for (let i = digits.length - 1; i >= 0; i--) {
56
+ let d = digits.charCodeAt(i) - 48;
57
+ if (double) {
58
+ d *= 2;
59
+ if (d > 9) d -= 9;
60
+ }
61
+ sum += d;
62
+ double = !double;
63
+ }
64
+ return sum % 10 === 0;
65
+ }
66
+ __name(luhnValid, "luhnValid");
67
+ function cardBrand(digits) {
68
+ const p2 = Number(digits.slice(0, 2));
69
+ const p3 = Number(digits.slice(0, 3));
70
+ const p4 = Number(digits.slice(0, 4));
71
+ const p6 = Number(digits.slice(0, 6));
72
+ const len = digits.length;
73
+ if ([
74
+ 401178,
75
+ 401179,
76
+ 431274,
77
+ 438935,
78
+ 451416,
79
+ 457393,
80
+ 457631,
81
+ 457632,
82
+ 504175,
83
+ 627780,
84
+ 636297,
85
+ 636368
86
+ ].includes(p6)) return "elo";
87
+ if (p6 >= 506699 && p6 <= 506778 || p6 >= 509e3 && p6 <= 509999 || p6 >= 650031 && p6 <= 650051) return "elo";
88
+ if (p6 === 606282 || p4 === 3841) return "hipercard";
89
+ if (digits.startsWith("4") && (len === 13 || len === 16 || len === 19)) return "visa";
90
+ if ((p2 >= 51 && p2 <= 55 || p4 >= 2221 && p4 <= 2720) && len === 16) return "mastercard";
91
+ if ((p2 === 34 || p2 === 37) && len === 15) return "amex";
92
+ if ((p4 === 6011 || p2 === 65 || p3 >= 644 && p3 <= 649) && len >= 16) return "discover";
93
+ if ((p2 === 36 || p2 === 38 || p3 >= 300 && p3 <= 305) && len >= 14) return "diners";
94
+ if (p4 >= 3528 && p4 <= 3589 && len >= 16) return "jcb";
95
+ if ((p2 === 62 || p2 === 81) && len >= 16) return "unionpay";
96
+ if ((p4 === 5018 || p4 === 5020 || p4 === 5038 || p4 === 6304 || p4 === 6759 || p4 === 6761) && len >= 12) return "maestro";
97
+ return void 0;
98
+ }
99
+ __name(cardBrand, "cardBrand");
100
+ function cpfValid(raw) {
101
+ const d = raw.replace(/\D/g, "");
102
+ if (d.length !== 11 || /^(\d)\1{10}$/.test(d)) return false;
103
+ const check = /* @__PURE__ */ __name((len) => {
104
+ let sum = 0;
105
+ for (let i = 0; i < len; i++) sum += (d.charCodeAt(i) - 48) * (len + 1 - i);
106
+ const r = sum * 10 % 11;
107
+ return r === 10 ? 0 : r;
108
+ }, "check");
109
+ return check(9) === d.charCodeAt(9) - 48 && check(10) === d.charCodeAt(10) - 48;
110
+ }
111
+ __name(cpfValid, "cpfValid");
112
+ function cnpjValid(raw) {
113
+ const c = raw.replace(/[.\-/\s]/g, "").toUpperCase();
114
+ if (!/^[0-9A-Z]{12}\d{2}$/.test(c) || /^(\d)\1{13}$/.test(c)) return false;
115
+ const value = /* @__PURE__ */ __name((i) => c.charCodeAt(i) - 48, "value");
116
+ const check = /* @__PURE__ */ __name((len) => {
117
+ const weights = len === 12 ? [
118
+ 5,
119
+ 4,
120
+ 3,
121
+ 2,
122
+ 9,
123
+ 8,
124
+ 7,
125
+ 6,
126
+ 5,
127
+ 4,
128
+ 3,
129
+ 2
130
+ ] : [
131
+ 6,
132
+ 5,
133
+ 4,
134
+ 3,
135
+ 2,
136
+ 9,
137
+ 8,
138
+ 7,
139
+ 6,
140
+ 5,
141
+ 4,
142
+ 3,
143
+ 2
144
+ ];
145
+ let sum = 0;
146
+ for (let i = 0; i < len; i++) sum += value(i) * (weights[i] ?? 0);
147
+ const r = sum % 11;
148
+ return r < 2 ? 0 : 11 - r;
149
+ }, "check");
150
+ return check(12) === value(12) && check(13) === value(13);
151
+ }
152
+ __name(cnpjValid, "cnpjValid");
153
+ function ssnValid(raw) {
154
+ const m = /^(\d{3})[- ](\d{2})[- ](\d{4})$/.exec(raw);
155
+ if (!m) return false;
156
+ const area = m[1] ?? "";
157
+ const group = m[2] ?? "";
158
+ const serial = m[3] ?? "";
159
+ if (area === "000" || area === "666" || area.startsWith("9")) return false;
160
+ if (group === "00" || serial === "0000") return false;
161
+ if ([
162
+ "078-05-1120",
163
+ "219-09-9999",
164
+ "123-45-6789"
165
+ ].includes(`${area}-${group}-${serial}`)) return false;
166
+ return true;
167
+ }
168
+ __name(ssnValid, "ssnValid");
169
+ var IBAN_LENGTHS = {
170
+ AD: 24,
171
+ AE: 23,
172
+ AT: 20,
173
+ BE: 16,
174
+ BG: 22,
175
+ BH: 22,
176
+ BR: 29,
177
+ CH: 21,
178
+ CR: 22,
179
+ CY: 28,
180
+ CZ: 24,
181
+ DE: 22,
182
+ DK: 18,
183
+ DO: 28,
184
+ EE: 20,
185
+ ES: 24,
186
+ FI: 18,
187
+ FO: 18,
188
+ FR: 27,
189
+ GB: 22,
190
+ GI: 23,
191
+ GL: 18,
192
+ GR: 27,
193
+ GT: 28,
194
+ HR: 21,
195
+ HU: 28,
196
+ IE: 22,
197
+ IL: 23,
198
+ IS: 26,
199
+ IT: 27,
200
+ JO: 30,
201
+ KW: 30,
202
+ KZ: 20,
203
+ LB: 28,
204
+ LI: 21,
205
+ LT: 20,
206
+ LU: 20,
207
+ LV: 21,
208
+ MC: 27,
209
+ MD: 24,
210
+ ME: 22,
211
+ MK: 19,
212
+ MR: 27,
213
+ MT: 31,
214
+ MU: 30,
215
+ NL: 18,
216
+ NO: 15,
217
+ PK: 24,
218
+ PL: 28,
219
+ PS: 29,
220
+ PT: 25,
221
+ QA: 29,
222
+ RO: 24,
223
+ RS: 22,
224
+ SA: 24,
225
+ SE: 24,
226
+ SI: 19,
227
+ SK: 24,
228
+ SM: 27,
229
+ TN: 24,
230
+ TR: 26,
231
+ UA: 29,
232
+ VG: 24,
233
+ XK: 20
234
+ };
235
+ function ibanValid(raw) {
236
+ const s = raw.replace(/\s/g, "").toUpperCase();
237
+ if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(s)) return false;
238
+ const expected = IBAN_LENGTHS[s.slice(0, 2)];
239
+ if (expected !== void 0 && s.length !== expected) return false;
240
+ if (expected === void 0) return false;
241
+ const rearranged = s.slice(4) + s.slice(0, 4);
242
+ let rem = 0;
243
+ for (const ch of rearranged) {
244
+ const v = ch >= "A" ? String(ch.charCodeAt(0) - 55) : ch;
245
+ for (const digit of v) rem = (rem * 10 + (digit.charCodeAt(0) - 48)) % 97;
246
+ }
247
+ return rem === 1;
248
+ }
249
+ __name(ibanValid, "ibanValid");
250
+ function phoneValid(raw) {
251
+ const digits = raw.replace(/\D/g, "");
252
+ if (raw.trim().startsWith("+")) return digits.length >= 8 && digits.length <= 15;
253
+ if (digits.length < 10 || digits.length > 11) return false;
254
+ if (/^\(?\d{2}\)?[\s.-]?9?\d{4}[\s.-]\d{4}$/.test(raw.trim())) return true;
255
+ if (/^\(?\d{3}\)?[\s.-]?\d{3}[\s.-]\d{4}$/.test(raw.trim())) return true;
256
+ return false;
257
+ }
258
+ __name(phoneValid, "phoneValid");
259
+ var PATTERNS = [
260
+ {
261
+ type: "email",
262
+ re: /(?<![\w.+-])[A-Za-z0-9](?:[A-Za-z0-9._%+-]{0,63})@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,62})\.)+[A-Za-z]{2,24}(?![\w-])/g,
263
+ score: 0.99
264
+ },
265
+ {
266
+ type: "iban",
267
+ re: /(?<![A-Za-z0-9])[A-Z]{2}\d{2}(?: ?[A-Z0-9]){11,30}(?![A-Za-z0-9])/g,
268
+ valid: ibanValid,
269
+ score: 0.99
270
+ },
271
+ {
272
+ type: "cnpj",
273
+ re: /(?<![\w./-])[0-9A-Z]{2}\.?[0-9A-Z]{3}\.?[0-9A-Z]{3}\/?[0-9A-Z]{4}-?\d{2}(?![\w/-])/g,
274
+ valid: /* @__PURE__ */ __name((m) => cnpjValid(m) && (/^\d/.test(m.replace(/\D/g, "")) || /[./-]/.test(m)) && !/^[A-Z]+$/.test(m), "valid"),
275
+ score: 0.98
276
+ },
277
+ {
278
+ type: "cpf",
279
+ re: /(?<![\w./-])\d{3}\.?\d{3}\.?\d{3}-?\d{2}(?![\w/-])/g,
280
+ valid: cpfValid,
281
+ score: 0.95
282
+ },
283
+ {
284
+ type: "credit_card",
285
+ re: /(?<![\d-])\d(?:[ -]?\d){11,18}(?![\d-])/g,
286
+ valid: /* @__PURE__ */ __name((m) => {
287
+ const digits = m.replace(/\D/g, "");
288
+ if (/ /.test(m) && /-/.test(m)) return false;
289
+ return digits.length >= 13 && digits.length <= 19 && luhnValid(digits) && !!cardBrand(digits);
290
+ }, "valid"),
291
+ score: 0.97
292
+ },
293
+ {
294
+ type: "ssn",
295
+ re: /(?<![\d-])\d{3}[- ]\d{2}[- ]\d{4}(?![\d-])/g,
296
+ valid: ssnValid,
297
+ score: 0.85
298
+ },
299
+ {
300
+ type: "phone",
301
+ re: /(?<![\w+])(?:\+\d{1,3}[\s.-]?)?\(?\d{2,4}\)?[\s.-]?\d{3,5}[\s.-]\d{4}(?![\w])/g,
302
+ valid: phoneValid,
303
+ score: 0.75
304
+ },
305
+ {
306
+ type: "ip_address",
307
+ re: /(?<![\d.])(?:\d{1,3}\.){3}\d{1,3}(?![\d.])/g,
308
+ valid: /* @__PURE__ */ __name((m) => isIPv4(m) && !m.startsWith("0.") && m !== "127.0.0.1", "valid"),
309
+ score: 0.6
310
+ }
311
+ ];
312
+ function detectPii(text, types = PII_TYPES) {
313
+ const want = new Set(types);
314
+ const out = [];
315
+ for (const p of PATTERNS) {
316
+ if (!want.has(p.type)) continue;
317
+ p.re.lastIndex = 0;
318
+ for (let m = p.re.exec(text); m; m = p.re.exec(text)) {
319
+ const value = m[0];
320
+ if (p.valid && !p.valid(value)) continue;
321
+ out.push({
322
+ detector: "pii",
323
+ category: `pii.${p.type}`,
324
+ start: m.index,
325
+ end: m.index + value.length,
326
+ score: p.score,
327
+ value,
328
+ spanned: true
329
+ });
330
+ }
331
+ }
332
+ return out;
333
+ }
334
+ __name(detectPii, "detectPii");
335
+
336
+ // src/guardrails/detectors/secrets.ts
337
+ function entropy(s) {
338
+ if (!s) return 0;
339
+ const counts = /* @__PURE__ */ new Map();
340
+ for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
341
+ let h = 0;
342
+ for (const c of counts.values()) {
343
+ const p = c / s.length;
344
+ h -= p * Math.log2(p);
345
+ }
346
+ return h;
347
+ }
348
+ __name(entropy, "entropy");
349
+ var PATTERNS2 = [
350
+ {
351
+ type: "private_key",
352
+ re: /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----|$)/g,
353
+ score: 0.99
354
+ },
355
+ {
356
+ type: "anthropic_key",
357
+ re: /(?<![\w-])sk-ant-(?:api|admin|oat)\d{0,2}-?[A-Za-z0-9_-]{20,}/g,
358
+ score: 0.99
359
+ },
360
+ {
361
+ type: "openai_key",
362
+ re: /(?<![\w-])sk-(?!ant-)(?:proj-|svcacct-|admin-|None-)?[A-Za-z0-9_-]{20,}/g,
363
+ score: 0.95,
364
+ valid: /* @__PURE__ */ __name((v) => entropy(v) > 3.5, "valid")
365
+ },
366
+ {
367
+ type: "aws_access_key",
368
+ re: /(?<![A-Z0-9])(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}(?![A-Z0-9])/g,
369
+ score: 0.98
370
+ },
371
+ {
372
+ type: "aws_secret_key",
373
+ re: /(?:aws_?secret_?access_?key|aws_?secret|secretaccesskey)["']?\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,
374
+ group: 1,
375
+ score: 0.97
376
+ },
377
+ {
378
+ type: "github_token",
379
+ re: /(?<![\w])(?:(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,255}|github_pat_[A-Za-z0-9_]{22,255})/g,
380
+ score: 0.99
381
+ },
382
+ {
383
+ type: "gitlab_token",
384
+ re: /(?<![\w-])glpat-[A-Za-z0-9_-]{20,}/g,
385
+ score: 0.99
386
+ },
387
+ {
388
+ type: "slack_token",
389
+ re: /(?<![\w-])(?:xox[abprseo]-[A-Za-z0-9-]{10,}|https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/]{20,})/g,
390
+ score: 0.98
391
+ },
392
+ {
393
+ type: "google_api_key",
394
+ re: /(?<![\w-])AIza[0-9A-Za-z_-]{35}(?![\w-])/g,
395
+ score: 0.97
396
+ },
397
+ {
398
+ type: "stripe_key",
399
+ re: /(?<![\w])(?:sk|rk|pk)_(?:live|test)_[0-9A-Za-z]{16,}/g,
400
+ score: 0.97
401
+ },
402
+ {
403
+ type: "jwt",
404
+ re: /(?<![\w-])eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?![\w-])/g,
405
+ score: 0.9
406
+ },
407
+ {
408
+ type: "bearer_token",
409
+ re: /\bbearer\s+([A-Za-z0-9._~+/-]{20,}=*)/gi,
410
+ group: 1,
411
+ score: 0.85,
412
+ valid: /* @__PURE__ */ __name((v) => entropy(v) > 3.5, "valid")
413
+ },
414
+ {
415
+ type: "generic_secret",
416
+ re: /\b(?:api[_-]?key|api[_-]?secret|client[_-]?secret|secret[_-]?key|access[_-]?token|auth[_-]?token|password|passwd|senha)\b["']?\s*[:=]\s*["']?([^\s"',;]{8,200})/gi,
417
+ group: 1,
418
+ score: 0.7,
419
+ // Words and placeholders ("changeme", "<your-key>", "${API_KEY}") are not secrets.
420
+ valid: /* @__PURE__ */ __name((v) => entropy(v) >= 3 && !/^[<{$[]/.test(v) && !/^(?:x+|\*+|\.+)$/i.test(v), "valid")
421
+ }
422
+ ];
423
+ function detectSecrets(text, types = SECRET_TYPES) {
424
+ const want = new Set(types);
425
+ const out = [];
426
+ for (const p of PATTERNS2) {
427
+ if (!want.has(p.type)) continue;
428
+ p.re.lastIndex = 0;
429
+ for (let m = p.re.exec(text); m; m = p.re.exec(text)) {
430
+ if (m[0].length === 0) {
431
+ p.re.lastIndex++;
432
+ continue;
433
+ }
434
+ const group = p.group ?? 0;
435
+ const value = m[group];
436
+ if (!value) continue;
437
+ if (p.valid && !p.valid(value)) continue;
438
+ const start = m.index + (group === 0 ? 0 : m[0].lastIndexOf(value));
439
+ out.push({
440
+ detector: "secrets",
441
+ category: `secret.${p.type}`,
442
+ start,
443
+ end: start + value.length,
444
+ score: p.score,
445
+ value,
446
+ spanned: true
447
+ });
448
+ }
449
+ }
450
+ return out;
451
+ }
452
+ __name(detectSecrets, "detectSecrets");
453
+
454
+ // src/guardrails/detectors/injection.ts
455
+ var W = String.raw`[\s\S]{0,40}?`;
456
+ var INJECTION_SIGNALS = [
457
+ // Instruction override.
458
+ {
459
+ id: "ignore_previous",
460
+ re: new RegExp(String.raw`\b(?:ignore|disregard|forget|override|bypass|skip)\b${W}\b(?:all\s+|any\s+|the\s+|your\s+|my\s+)?(?:previous|prior|above|earlier|preceding|original|system|initial)\b${W}\b(?:instructions?|prompts?|rules?|directions?|guidelines?|messages?|context)\b`, "gi"),
461
+ weight: 0.75
462
+ },
463
+ {
464
+ id: "ignore_previous_pt",
465
+ re: new RegExp(String.raw`\b(?:ignore|ignora|desconsidere|desconsidera|esque[çc]a|esquece|descarte)\b${W}\b(?:as\s+|todas\s+as\s+|suas\s+|tuas\s+)?(?:instru[çc][õo]es|regras|orienta[çc][õo]es|mensagens|prompts?)\b${W}\b(?:anteriores|acima|pr[ée]vias|do\s+sistema|originais)\b`, "gi"),
466
+ weight: 0.75
467
+ },
468
+ {
469
+ id: "ignore_previous_es",
470
+ re: new RegExp(String.raw`\b(?:ignora|ignore|olvida|olvide|descarta)\b${W}\b(?:las\s+|todas\s+las\s+|tus\s+)?(?:instrucciones|reglas|indicaciones)\b${W}\b(?:anteriores|previas|del\s+sistema)\b`, "gi"),
471
+ weight: 0.75
472
+ },
473
+ {
474
+ id: "new_instructions",
475
+ re: /\b(?:new|updated|real|actual|true)\s+(?:system\s+)?instructions?\s*(?::|follow|are)|\bnovas\s+instru[çc][õo]es\s*:|\bnuevas\s+instrucciones\s*:/gi,
476
+ weight: 0.45
477
+ },
478
+ // Role hijack / persona jailbreaks.
479
+ {
480
+ id: "role_hijack",
481
+ re: /\b(?:you\s+are\s+now|from\s+now\s+on\s+you\s+(?:are|will)|act\s+as\s+(?:an?\s+)?(?:unrestricted|unfiltered|evil|jailbroken)|pretend\s+(?:to\s+be|you\s+are)\s+(?:an?\s+)?(?:unrestricted|unfiltered|different\s+ai)|a\s+partir\s+de\s+agora\s+voc[êe]\s+(?:[ée]|ser[áa])|voc[êe]\s+agora\s+[ée]|ahora\s+eres)\b/gi,
482
+ weight: 0.45
483
+ },
484
+ {
485
+ id: "jailbreak_persona",
486
+ re: /\b(?:DAN|do\s+anything\s+now|developer\s+mode|god\s+mode|jailbreak(?:ed)?|STAN|DUDE\s+mode|AIM\s+mode|no\s+(?:restrictions|filters|guidelines|limits)\s+mode|modo\s+desenvolvedor|sem\s+restri[çc][õo]es)\b/g,
487
+ weight: 0.55
488
+ },
489
+ // Prompt / secret extraction.
490
+ {
491
+ id: "prompt_leak",
492
+ re: /\b(?:reveal|print|show|repeat|output|display|leak|tell\s+me|write\s+out)\b[\s\S]{0,30}?\b(?:your|the)\s+(?:system\s+prompt|initial\s+(?:prompt|instructions)|hidden\s+(?:prompt|instructions)|instructions\s+above|developer\s+message)\b|\b(?:mostre|revele|repita|imprima)\b[\s\S]{0,30}?\b(?:seu|o)\s+prompt\s+(?:do\s+sistema|inicial)\b/gi,
493
+ weight: 0.6
494
+ },
495
+ // Role / delimiter spoofing: chat-template tokens and fake headers.
496
+ {
497
+ id: "role_tokens",
498
+ re: /<\|(?:im_start|im_end|system|endoftext|start_header_id|end_header_id|eot_id)\|>|\[\/?INST\]|<<\/?SYS>>|<\/?(?:system|assistant|developer)>|^\s*#{2,}\s*(?:system|assistant|developer)\s*(?:prompt|message)?\s*:?\s*$/gim,
499
+ weight: 0.55
500
+ },
501
+ {
502
+ id: "fake_system",
503
+ re: /^\s*(?:system|assistant|developer)\s*(?:message|prompt|note|override)?\s*:\s*\S/gim,
504
+ weight: 0.3
505
+ },
506
+ // Indirect injection aimed at the agent reading the content.
507
+ {
508
+ id: "addressed_to_ai",
509
+ re: /\b(?:(?:note|message|instructions?)\s+(?:to|for)\s+(?:the\s+)?(?:ai|assistant|llm|agent|model|chatbot)|(?:ai|assistant|llm|agent)s?\s*(?:reading|processing|summari[sz]ing)\s+this|if\s+you\s+are\s+an?\s+(?:ai|assistant|llm|language\s+model|agent)|aten[çc][ãa]o\s*,?\s*(?:ia|assistente|agente)|se\s+voc[êe]\s+[ée]\s+(?:uma\s+)?(?:ia|intelig[êe]ncia\s+artificial|assistente))\b/gi,
510
+ weight: 0.55
511
+ },
512
+ {
513
+ id: "tool_directive",
514
+ re: /\b(?:call|invoke|use|run|execute|chame|use|execute)\s+(?:the\s+|a\s+|o\s+|a\s+ferramenta\s+)?[`'"]?[\w-]+(?:__[\w-]+)[`'"]?\s*(?:tool|function|ferramenta)?|\b(?:send|forward|email|post|upload|exfiltrate|envie|encaminhe)\b[\s\S]{0,40}?\b(?:(?:all|every|the)\s+)?(?:(?:user'?s?|their|this|todos\s+os|os)\s+)?(?:emails?|files?|documents?|credentials?|passwords?|secrets?|tokens?|keys?|conversation|chat\s+history|dados|arquivos|senhas)\b[\s\S]{0,40}?\b(?:to|para)\s+\S+@\S+|\bto\s+https?:\/\//gi,
515
+ weight: 0.45
516
+ },
517
+ {
518
+ id: "secrecy",
519
+ re: /\b(?:do\s+not|don'?t|never)\s+(?:tell|inform|mention|reveal|show|alert|notify)\s+(?:this\s+to\s+)?(?:the\s+)?user\b|\bwithout\s+(?:telling|informing|notifying)\s+the\s+user\b|\bn[ãa]o\s+(?:conte|informe|mencione|avise)\s+(?:ao|o)\s+usu[áa]rio\b/gi,
520
+ weight: 0.5
521
+ },
522
+ // Exfiltration through rendered markdown/HTML.
523
+ {
524
+ id: "exfil_markdown",
525
+ re: /!\[[^\]]*\]\(\s*https?:\/\/[^)\s]+\?[^)\s]*=[^)\s]*(?:\{|%7B|\$|<)?[^)]*\)|<img\b[^>]*\bsrc\s*=\s*["']?https?:\/\/[^"'>\s]+\?[^"'>\s]*=/gi,
526
+ weight: 0.4
527
+ },
528
+ {
529
+ id: "html_comment_instructions",
530
+ re: /<!--[\s\S]{0,500}?\b(?:ignore|instruction|assistant|ai|agent|system|prompt|instru[çc][ãa]o|assistente)\b[\s\S]{0,500}?-->/gi,
531
+ weight: 0.45
532
+ }
533
+ ];
534
+ var TAG_CHARS = /[\u{E0000}-\u{E007F}]+/gu;
535
+ var BIDI = /[\u202A-\u202E\u2066-\u2069]/g;
536
+ var ZERO_WIDTH_RUN = /[\u200B-\u200D\u2060\uFEFF]{3,}/g;
537
+ function decodeBase64Ascii(b64) {
538
+ try {
539
+ return atob(b64);
540
+ } catch {
541
+ return void 0;
542
+ }
543
+ }
544
+ __name(decodeBase64Ascii, "decodeBase64Ascii");
545
+ function decodeTagChars(s) {
546
+ let out = "";
547
+ for (const ch of s) {
548
+ const cp = ch.codePointAt(0) ?? 0;
549
+ if (cp >= 917536 && cp <= 917630) out += String.fromCharCode(cp - 917504);
550
+ }
551
+ return out;
552
+ }
553
+ __name(decodeTagChars, "decodeTagChars");
554
+ function matchSignals(text, offset, into, weights) {
555
+ for (const s of INJECTION_SIGNALS) {
556
+ s.re.lastIndex = 0;
557
+ for (let m = s.re.exec(text); m; m = s.re.exec(text)) {
558
+ if (m[0].length === 0) {
559
+ s.re.lastIndex++;
560
+ continue;
561
+ }
562
+ weights.set(s.id, Math.max(weights.get(s.id) ?? 0, s.weight));
563
+ const list = into.get(s.id) ?? [];
564
+ list.push({
565
+ detector: "injection",
566
+ category: `injection.${s.id}`,
567
+ start: offset < 0 ? m.index : offset,
568
+ end: offset < 0 ? m.index + m[0].length : offset,
569
+ score: s.weight,
570
+ value: m[0],
571
+ spanned: offset < 0
572
+ });
573
+ into.set(s.id, list);
574
+ }
575
+ }
576
+ }
577
+ __name(matchSignals, "matchSignals");
578
+ function scoreInjection(text, threshold = 0.5) {
579
+ const byId = /* @__PURE__ */ new Map();
580
+ const weights = /* @__PURE__ */ new Map();
581
+ matchSignals(text, -1, byId, weights);
582
+ TAG_CHARS.lastIndex = 0;
583
+ for (let m = TAG_CHARS.exec(text); m; m = TAG_CHARS.exec(text)) {
584
+ weights.set("hidden_unicode", 0.8);
585
+ const list = byId.get("hidden_unicode") ?? [];
586
+ list.push({
587
+ detector: "injection",
588
+ category: "injection.hidden_unicode",
589
+ start: m.index,
590
+ end: m.index + m[0].length,
591
+ score: 0.8,
592
+ value: m[0],
593
+ spanned: true
594
+ });
595
+ byId.set("hidden_unicode", list);
596
+ const decoded = decodeTagChars(m[0]);
597
+ if (decoded) matchSignals(decoded, m.index, byId, weights);
598
+ }
599
+ for (const re of [
600
+ BIDI,
601
+ ZERO_WIDTH_RUN
602
+ ]) {
603
+ re.lastIndex = 0;
604
+ for (let m = re.exec(text); m; m = re.exec(text)) {
605
+ weights.set("invisible_chars", Math.max(weights.get("invisible_chars") ?? 0, 0.3));
606
+ const list = byId.get("invisible_chars") ?? [];
607
+ list.push({
608
+ detector: "injection",
609
+ category: "injection.invisible_chars",
610
+ start: m.index,
611
+ end: m.index + m[0].length,
612
+ score: 0.3,
613
+ value: m[0],
614
+ spanned: true
615
+ });
616
+ byId.set("invisible_chars", list);
617
+ }
618
+ }
619
+ const B64 = /(?<![A-Za-z0-9+/=])[A-Za-z0-9+/]{40,}={0,2}(?![A-Za-z0-9+/=])/g;
620
+ for (let m = B64.exec(text); m; m = B64.exec(text)) {
621
+ const decoded = decodeBase64Ascii(m[0]);
622
+ if (decoded === void 0) continue;
623
+ if (!/^[\x20-\x7E\s]+$/.test(decoded)) continue;
624
+ const inner = /* @__PURE__ */ new Map();
625
+ const innerWeights = /* @__PURE__ */ new Map();
626
+ matchSignals(decoded, m.index, inner, innerWeights);
627
+ if (innerWeights.size > 0) {
628
+ weights.set("encoded_instructions", 0.6);
629
+ byId.set("encoded_instructions", [
630
+ {
631
+ detector: "injection",
632
+ category: "injection.encoded_instructions",
633
+ start: m.index,
634
+ end: m.index + m[0].length,
635
+ score: 0.6,
636
+ value: m[0],
637
+ spanned: true
638
+ }
639
+ ]);
640
+ }
641
+ }
642
+ let keep = 1;
643
+ for (const w of weights.values()) keep *= 1 - w;
644
+ const score = Math.round((1 - keep) * 1e3) / 1e3;
645
+ if (score < threshold) return {
646
+ score,
647
+ findings: []
648
+ };
649
+ const findings = [
650
+ ...byId.values()
651
+ ].flat().map((f) => ({
652
+ ...f,
653
+ score: Math.max(f.score, score)
654
+ }));
655
+ return {
656
+ score,
657
+ findings
658
+ };
659
+ }
660
+ __name(scoreInjection, "scoreInjection");
661
+
662
+ // src/guardrails/detectors/tool-poisoning.ts
663
+ var POISON_SIGNALS = [
664
+ {
665
+ id: "important_tag",
666
+ re: /<\s*\/?\s*(?:important|system|instructions?|secret|hidden|critical)\s*>/gi,
667
+ weight: 0.55
668
+ },
669
+ {
670
+ id: "sensitive_files",
671
+ re: /(?:~\/\.ssh|id_rsa|id_ed25519|\.aws\/credentials|\.env\b|mcp\.json|\.netrc|\.npmrc|\.pypirc|\.git-credentials|\/etc\/passwd|\/etc\/shadow|kubeconfig|\.kube\/config|credentials\.json|cookies\.sqlite|wallet\.dat)/gi,
672
+ weight: 0.65
673
+ },
674
+ {
675
+ id: "read_before_use",
676
+ re: /\b(?:before|prior\s+to)\s+(?:using|calling|invoking|running)\s+(?:this|any|the)\s+(?:tool|function)\b[\s\S]{0,80}?\b(?:read|open|cat|fetch|call|send|include|pass|load)\b/gi,
677
+ weight: 0.5
678
+ },
679
+ {
680
+ id: "hidden_parameter",
681
+ re: /\b(?:pass|put|include|add|send|store)\b[\s\S]{0,40}?\b(?:content|contents|value|output|result|key|token|file)s?\b[\s\S]{0,40}?\b(?:as|in|into)\s+(?:the\s+)?[`'"]?(?:sidenote|side_note|note|notes|metadata|context|extra|comment|debug)[`'"]?\b/gi,
682
+ weight: 0.55
683
+ },
684
+ {
685
+ id: "cross_tool",
686
+ re: /\b(?:when|whenever|if)\s+(?:the\s+)?[`'"]?[\w.-]+[`'"]?\s+(?:tool|function)\s+is\s+(?:called|used|invoked)\b|\b(?:instead\s+of|rather\s+than)\s+(?:calling|using)\s+(?:the\s+)?[`'"]?[\w.-]+[`'"]?\s*(?:tool|function)?\b|\b(?:all|every)\s+(?:emails?|messages?)\s+(?:must|should)\s+(?:be\s+)?(?:sent|forwarded|bcc'?d|cc'?d)\s+to\b/gi,
687
+ weight: 0.5
688
+ },
689
+ {
690
+ id: "override_behaviour",
691
+ re: /\b(?:this\s+(?:tool|instruction)\s+(?:takes|has)\s+(?:precedence|priority)|overrides?\s+(?:all\s+)?(?:other|previous)\s+(?:tools?|instructions?)|very\s+(?:very\s+)?important\s*:?|you\s+must\s+(?:always|first|never))\b/gi,
692
+ weight: 0.35
693
+ }
694
+ ];
695
+ var LONG_DESCRIPTION = 2e3;
696
+ function scoreToolText(text, threshold = 0.5) {
697
+ const weights = /* @__PURE__ */ new Map();
698
+ const findings = [];
699
+ for (const s of POISON_SIGNALS) {
700
+ s.re.lastIndex = 0;
701
+ for (let m = s.re.exec(text); m; m = s.re.exec(text)) {
702
+ weights.set(s.id, s.weight);
703
+ findings.push({
704
+ detector: "tool_poisoning",
705
+ category: `tool_poisoning.${s.id}`,
706
+ start: m.index,
707
+ end: m.index + m[0].length,
708
+ score: s.weight,
709
+ value: m[0],
710
+ spanned: true
711
+ });
712
+ }
713
+ }
714
+ const injection = scoreInjection(text, 0);
715
+ for (const f of injection.findings) {
716
+ const id = f.category.slice("injection.".length);
717
+ const sig = INJECTION_SIGNALS.find((s) => s.id === id);
718
+ weights.set(`inj:${id}`, sig?.weight ?? f.score);
719
+ findings.push({
720
+ ...f,
721
+ detector: "tool_poisoning",
722
+ category: `tool_poisoning.${id}`
723
+ });
724
+ }
725
+ if (text.length > LONG_DESCRIPTION) weights.set("long_description", 0.2);
726
+ let keep = 1;
727
+ for (const w of weights.values()) keep *= 1 - w;
728
+ const score = Math.round((1 - keep) * 1e3) / 1e3;
729
+ return score >= threshold ? {
730
+ score,
731
+ findings
732
+ } : {
733
+ score,
734
+ findings: []
735
+ };
736
+ }
737
+ __name(scoreToolText, "scoreToolText");
738
+ function toolText(tool) {
739
+ const parts = [];
740
+ if (tool.title) parts.push(tool.title);
741
+ if (tool.description) parts.push(tool.description);
742
+ const walk = /* @__PURE__ */ __name((schema, depth) => {
743
+ if (!schema || typeof schema !== "object" || depth > 6) return;
744
+ const s = schema;
745
+ if (typeof s.description === "string") parts.push(s.description);
746
+ if (typeof s.title === "string") parts.push(s.title);
747
+ for (const key of [
748
+ "properties",
749
+ "definitions",
750
+ "$defs"
751
+ ]) {
752
+ const props = s[key];
753
+ if (props && typeof props === "object") for (const [name, child] of Object.entries(props)) {
754
+ parts.push(name);
755
+ walk(child, depth + 1);
756
+ }
757
+ }
758
+ for (const key of [
759
+ "items",
760
+ "additionalProperties"
761
+ ]) walk(s[key], depth + 1);
762
+ for (const key of [
763
+ "anyOf",
764
+ "oneOf",
765
+ "allOf"
766
+ ]) {
767
+ const list = s[key];
768
+ if (Array.isArray(list)) for (const child of list) walk(child, depth + 1);
769
+ }
770
+ if (Array.isArray(s.enum)) {
771
+ for (const e of s.enum) if (typeof e === "string") parts.push(e);
772
+ }
773
+ if (typeof s.default === "string") parts.push(s.default);
774
+ }, "walk");
775
+ walk(tool.inputSchema, 0);
776
+ return parts.join("\n");
777
+ }
778
+ __name(toolText, "toolText");
779
+
780
+ // src/guardrails/detectors/custom.ts
781
+ function compileFlagged(pattern) {
782
+ return pattern.startsWith("(?i)") ? new RegExp(pattern.slice(4), "i") : new RegExp(pattern);
783
+ }
784
+ __name(compileFlagged, "compileFlagged");
785
+ var compiled = /* @__PURE__ */ new Map();
786
+ function compileGuardPattern(pattern) {
787
+ let re = compiled.get(pattern);
788
+ if (!re) {
789
+ const base = compileFlagged(pattern);
790
+ re = new RegExp(base.source, `${base.flags.replace("g", "")}g`);
791
+ if (compiled.size > 5e3) compiled.clear();
792
+ compiled.set(pattern, re);
793
+ }
794
+ return re;
795
+ }
796
+ __name(compileGuardPattern, "compileGuardPattern");
797
+ function detectRegex(text, patterns, label = "custom") {
798
+ const out = [];
799
+ for (const p of patterns) {
800
+ let re;
801
+ try {
802
+ re = compileGuardPattern(p);
803
+ } catch {
804
+ continue;
805
+ }
806
+ re.lastIndex = 0;
807
+ let guard = 0;
808
+ for (let m = re.exec(text); m && guard < 1e3; m = re.exec(text), guard++) {
809
+ if (m[0].length === 0) {
810
+ re.lastIndex++;
811
+ continue;
812
+ }
813
+ out.push({
814
+ detector: "regex",
815
+ category: `regex.${label}`,
816
+ start: m.index,
817
+ end: m.index + m[0].length,
818
+ score: 1,
819
+ value: m[0],
820
+ spanned: true
821
+ });
822
+ }
823
+ }
824
+ return out;
825
+ }
826
+ __name(detectRegex, "detectRegex");
827
+ var COMBINING = /[\u0300-\u036f]/g;
828
+ function fold(s) {
829
+ let out = "";
830
+ for (const ch of s) {
831
+ const base = ch.normalize("NFD").replace(COMBINING, "");
832
+ const c = (base.length === ch.length ? base : ch).toLowerCase();
833
+ out += c.length === ch.length ? c : ch;
834
+ }
835
+ return out;
836
+ }
837
+ __name(fold, "fold");
838
+ var escapeRe = /* @__PURE__ */ __name((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "escapeRe");
839
+ function detectKeywords(text, words, label = "keywords") {
840
+ const clean = words.map((w) => fold(w.trim())).filter(Boolean);
841
+ if (clean.length === 0) return [];
842
+ const re = new RegExp(`(?<![\\p{L}\\p{N}_])(?:${clean.map(escapeRe).join("|")})(?![\\p{L}\\p{N}_])`, "gu");
843
+ const folded = fold(text);
844
+ const out = [];
845
+ for (let m = re.exec(folded); m; m = re.exec(folded)) {
846
+ out.push({
847
+ detector: "keywords",
848
+ category: `keywords.${label}`,
849
+ start: m.index,
850
+ end: m.index + m[0].length,
851
+ score: 1,
852
+ value: text.slice(m.index, m.index + m[0].length),
853
+ spanned: true
854
+ });
855
+ }
856
+ return out;
857
+ }
858
+ __name(detectKeywords, "detectKeywords");
859
+
860
+ // src/guardrails/glob.ts
861
+ var cache = /* @__PURE__ */ new Map();
862
+ function globToRegExp(pattern) {
863
+ let re = cache.get(pattern);
864
+ if (!re) {
865
+ const body = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
866
+ re = new RegExp(`^${body}$`);
867
+ cache.set(pattern, re);
868
+ }
869
+ return re;
870
+ }
871
+ __name(globToRegExp, "globToRegExp");
872
+ function matchesAny(value, patterns) {
873
+ if (!patterns || patterns.length === 0) return true;
874
+ return patterns.some((p) => globToRegExp(p).test(value));
875
+ }
876
+ __name(matchesAny, "matchesAny");
877
+
878
+ // src/guardrails/vault.ts
879
+ var Vault = class _Vault {
880
+ static {
881
+ __name(this, "Vault");
882
+ }
883
+ byValue = /* @__PURE__ */ new Map();
884
+ byToken = /* @__PURE__ */ new Map();
885
+ counters = /* @__PURE__ */ new Map();
886
+ /**
887
+ * Placeholder for `value` under `label` (e.g. `CREDIT_CARD`). Reversible placeholders look like
888
+ * `[CREDIT_CARD_1]`; one-way ones (tool results, answers, rules with `restore: false`) like
889
+ * `[REDACTED_CREDIT_CARD_1]`, so they can never be mistaken for a reversible one.
890
+ */
891
+ tokenFor(label, value, restorable) {
892
+ const ns = restorable ? label : `REDACTED_${label}`;
893
+ const key = `${ns}\0${value}`;
894
+ const existing = this.byValue.get(key);
895
+ if (existing) return existing;
896
+ const n = (this.counters.get(ns) ?? 0) + 1;
897
+ this.counters.set(ns, n);
898
+ const token = `[${ns}_${n}]`;
899
+ this.byValue.set(key, token);
900
+ this.byToken.set(token, {
901
+ value,
902
+ restorable
903
+ });
904
+ return token;
905
+ }
906
+ get size() {
907
+ return this.byToken.size;
908
+ }
909
+ get restorableCount() {
910
+ let n = 0;
911
+ for (const e of this.byToken.values()) if (e.restorable) n++;
912
+ return n;
913
+ }
914
+ /** Longest placeholder, for stream hold-back windows. */
915
+ get maxTokenLength() {
916
+ let n = 0;
917
+ for (const t of this.byToken.keys()) n = Math.max(n, t.length);
918
+ return n;
919
+ }
920
+ /** Puts restorable values back. `jsonString`: the text is inside a JSON string literal. */
921
+ restore(text, jsonString = false) {
922
+ if (this.byToken.size === 0 || !text.includes("[")) return text;
923
+ return text.replace(/\[([A-Z][A-Z0-9_]*_\d+)\]/g, (whole) => {
924
+ const entry = this.byToken.get(whole);
925
+ if (!entry?.restorable) return whole;
926
+ return jsonString ? JSON.stringify(entry.value).slice(1, -1) : entry.value;
927
+ });
928
+ }
929
+ /** Whether `text` ends with what could be the beginning of a placeholder (streaming). */
930
+ pendingPrefix(text) {
931
+ const i = text.lastIndexOf("[");
932
+ if (i === -1) return 0;
933
+ const tail = text.slice(i);
934
+ if (tail.length > this.maxTokenLength) return 0;
935
+ return /^\[[A-Z0-9_]*$/.test(tail) ? tail.length : 0;
936
+ }
937
+ /** Every entry, in the order the placeholders were minted. */
938
+ toJSON() {
939
+ return {
940
+ entries: [
941
+ ...this.byToken
942
+ ].map(([token, e]) => ({
943
+ token,
944
+ value: e.value,
945
+ restorable: e.restorable
946
+ }))
947
+ };
948
+ }
949
+ /** Rebuilds a vault from {@link toJSON}; placeholders keep their numbers and counters resume. */
950
+ static fromJSON(snapshot) {
951
+ const vault = new _Vault();
952
+ for (const { token, value, restorable } of snapshot.entries) {
953
+ const m = /^\[([A-Z][A-Z0-9_]*)_(\d+)\]$/.exec(token);
954
+ if (!m) continue;
955
+ const ns = m[1] ?? "";
956
+ const n = Number(m[2]);
957
+ vault.byValue.set(`${ns}\0${value}`, token);
958
+ vault.byToken.set(token, {
959
+ value,
960
+ restorable
961
+ });
962
+ vault.counters.set(ns, Math.max(vault.counters.get(ns) ?? 0, n));
963
+ }
964
+ return vault;
965
+ }
966
+ };
967
+ function labelFor(category) {
968
+ const tail = category.includes(".") ? category.slice(category.indexOf(".") + 1) : category;
969
+ const label = tail.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
970
+ return label || "REDACTED";
971
+ }
972
+ __name(labelFor, "labelFor");
973
+
974
+ // src/guardrails/engine.ts
975
+ var DEFAULT_MESSAGES = {
976
+ llm_request: "This message was blocked by your organization's AI guardrails.",
977
+ llm_response: "The answer was withheld by your organization's AI guardrails.",
978
+ tool_args: "This tool call was blocked by your organization's AI guardrails.",
979
+ tool_result: "The tool's result was withheld by your organization's AI guardrails.",
980
+ tool_description: "This tool was quarantined by your organization's AI guardrails."
981
+ };
982
+ var DEFAULT_APPROVAL_MESSAGE = "This needs approval under your organization's AI guardrails before it can go ahead.";
983
+ var INJECTION_REPLACEMENT = "[removed: suspected prompt injection]";
984
+ var ruleName = /* @__PURE__ */ __name((rule) => rule.name ?? rule.id, "ruleName");
985
+ function ruleApplies(rule, ctx) {
986
+ if (rule.enabled === false || !rule.stages.includes(ctx.stage)) return false;
987
+ const tools = rule.match?.tools;
988
+ if (tools?.length) {
989
+ const toolStage = ctx.stage === "tool_args" || ctx.stage === "tool_result" || ctx.stage === "tool_description";
990
+ if (!toolStage || !matchesAny(ctx.tool ?? "", tools)) return false;
991
+ }
992
+ return rule.when === void 0 || rule.when(ctx);
993
+ }
994
+ __name(ruleApplies, "ruleApplies");
995
+ function orderRules(rules) {
996
+ return [
997
+ ...rules
998
+ ].sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100) || ruleName(a).localeCompare(ruleName(b)));
999
+ }
1000
+ __name(orderRules, "orderRules");
1001
+ async function runDetector(spec, text, ctx) {
1002
+ if (!text) return [];
1003
+ switch (spec.kind) {
1004
+ case "pii":
1005
+ return detectPii(text, spec.types?.length ? spec.types : void 0);
1006
+ case "secrets":
1007
+ return detectSecrets(text, spec.types?.length ? spec.types : void 0);
1008
+ case "injection":
1009
+ return scoreInjection(text, spec.threshold ?? 0.5).findings;
1010
+ case "tool_poisoning":
1011
+ return scoreToolText(text, spec.threshold ?? 0.5).findings;
1012
+ case "regex":
1013
+ return detectRegex(text, spec.patterns, spec.label ?? "custom");
1014
+ case "keywords":
1015
+ return detectKeywords(text, spec.words, spec.label ?? "keywords");
1016
+ case "custom":
1017
+ return spec.detect(text, ctx);
1018
+ }
1019
+ }
1020
+ __name(runDetector, "runDetector");
1021
+ function spanLocator(rules) {
1022
+ const specs = rules.flatMap((r) => r.detectors).filter((d) => d.kind !== "custom" && d.kind !== "tool_poisoning");
1023
+ if (specs.length === 0) return void 0;
1024
+ return (text) => specs.flatMap((d) => {
1025
+ switch (d.kind) {
1026
+ case "pii":
1027
+ return detectPii(text, d.types?.length ? d.types : void 0);
1028
+ case "secrets":
1029
+ return detectSecrets(text, d.types?.length ? d.types : void 0);
1030
+ case "regex":
1031
+ return detectRegex(text, d.patterns);
1032
+ case "keywords":
1033
+ return detectKeywords(text, d.words);
1034
+ case "injection":
1035
+ return scoreInjection(text, 0).findings;
1036
+ default:
1037
+ return [];
1038
+ }
1039
+ });
1040
+ }
1041
+ __name(spanLocator, "spanLocator");
1042
+ function resolveOverlaps(findings, text) {
1043
+ const sorted = [
1044
+ ...findings
1045
+ ].filter((f) => f.spanned !== false && f.end > f.start).sort((a, b) => a.start - b.start || b.end - a.end || b.score - a.score);
1046
+ const out = [];
1047
+ for (const f of sorted) {
1048
+ const last = out[out.length - 1];
1049
+ if (!last || f.start >= last.end) {
1050
+ out.push({
1051
+ ...f
1052
+ });
1053
+ continue;
1054
+ }
1055
+ const longer = f.end - f.start > last.end - last.start || f.end - f.start === last.end - last.start && f.score > last.score ? f : last;
1056
+ const merged = {
1057
+ ...longer,
1058
+ start: last.start,
1059
+ end: Math.max(last.end, f.end)
1060
+ };
1061
+ if (text !== void 0) merged.value = text.slice(merged.start, merged.end);
1062
+ out[out.length - 1] = merged;
1063
+ }
1064
+ return out;
1065
+ }
1066
+ __name(resolveOverlaps, "resolveOverlaps");
1067
+ function replacementFor(f, vault, restorable) {
1068
+ if (f.replacement !== void 0) return f.replacement;
1069
+ if (f.detector === "injection" || f.detector === "tool_poisoning") return INJECTION_REPLACEMENT;
1070
+ return vault.tokenFor(labelFor(f.category), f.value, restorable);
1071
+ }
1072
+ __name(replacementFor, "replacementFor");
1073
+ function applyRedactions(text, findings, vault, restorable) {
1074
+ const whole = findings.find((f) => f.spanned === false);
1075
+ if (whole) {
1076
+ return {
1077
+ text: whole.replacement ?? `[removed: ${whole.category}]`,
1078
+ count: 1
1079
+ };
1080
+ }
1081
+ const spans = resolveOverlaps(findings, text);
1082
+ if (spans.length === 0) return {
1083
+ text,
1084
+ count: 0
1085
+ };
1086
+ let out = "";
1087
+ let at = 0;
1088
+ for (const f of spans) {
1089
+ out += text.slice(at, f.start) + replacementFor(f, vault, restorable);
1090
+ at = f.end;
1091
+ }
1092
+ return {
1093
+ text: out + text.slice(at),
1094
+ count: spans.length
1095
+ };
1096
+ }
1097
+ __name(applyRedactions, "applyRedactions");
1098
+ function memoKey(spec) {
1099
+ return spec.kind === "custom" ? `custom\0${spec.name}` : JSON.stringify(spec);
1100
+ }
1101
+ __name(memoKey, "memoKey");
1102
+ async function scan(rules, ctx, segments, vault = new Vault(), options = {}) {
1103
+ const started = performance.now();
1104
+ const applicable = orderRules(rules).filter((r) => ruleApplies(r, ctx));
1105
+ const result = {
1106
+ stage: ctx.stage,
1107
+ action: "allow",
1108
+ segments: segments.map((s) => s.text),
1109
+ changed: false,
1110
+ hits: [],
1111
+ vault,
1112
+ redactions: 0,
1113
+ latencyMs: 0
1114
+ };
1115
+ if (applicable.length === 0 || segments.length === 0) {
1116
+ result.latencyMs = performance.now() - started;
1117
+ return result;
1118
+ }
1119
+ const stageMessage = options.messages?.[ctx.stage] ?? DEFAULT_MESSAGES[ctx.stage];
1120
+ const restorableStage = (options.restorableStages ?? [
1121
+ "llm_request"
1122
+ ]).includes(ctx.stage);
1123
+ const memo = /* @__PURE__ */ new Map();
1124
+ const detect = /* @__PURE__ */ __name((spec, index) => {
1125
+ const key = `${index}\0${memoKey(spec)}`;
1126
+ let p = memo.get(key);
1127
+ if (!p) {
1128
+ p = Promise.resolve().then(() => runDetector(spec, segments[index]?.text ?? "", ctx));
1129
+ memo.set(key, p);
1130
+ }
1131
+ return p;
1132
+ }, "detect");
1133
+ const outcomes = [];
1134
+ for (const rule of applicable) {
1135
+ const sources = rule.match?.sources;
1136
+ const indexes = segments.map((s, i) => ({
1137
+ s,
1138
+ i
1139
+ })).filter(({ s }) => !sources?.length || !s.source || sources.includes(s.source)).map(({ i }) => i);
1140
+ const outcome = {
1141
+ rule,
1142
+ bySegment: /* @__PURE__ */ new Map()
1143
+ };
1144
+ if (rule.detectors.length === 0) {
1145
+ if (rule.action === "allow" || rule.action === "block" || rule.action === "approve") outcomes.push(outcome);
1146
+ if (rule.action === "allow") break;
1147
+ continue;
1148
+ }
1149
+ const jobs = rule.detectors.flatMap((spec) => indexes.map(async (i) => ({
1150
+ i,
1151
+ found: await detect(spec, i)
1152
+ })));
1153
+ const settled = await Promise.allSettled(jobs);
1154
+ for (const s of settled) {
1155
+ if (s.status === "rejected") {
1156
+ outcome.failed = s.reason?.message ?? String(s.reason);
1157
+ continue;
1158
+ }
1159
+ if (s.value.found.length === 0) continue;
1160
+ const list = outcome.bySegment.get(s.value.i) ?? [];
1161
+ list.push(...s.value.found);
1162
+ outcome.bySegment.set(s.value.i, list);
1163
+ }
1164
+ if (outcome.bySegment.size === 0 && !outcome.failed) continue;
1165
+ outcomes.push(outcome);
1166
+ if (rule.action === "allow" && outcome.bySegment.size > 0) break;
1167
+ }
1168
+ const redactBySegment = /* @__PURE__ */ new Map();
1169
+ for (const o of outcomes) {
1170
+ const { rule } = o;
1171
+ const name = ruleName(rule);
1172
+ const failMode = rule.options?.failMode ?? "open";
1173
+ let action = rule.action;
1174
+ if (o.failed) {
1175
+ result.hits.push({
1176
+ ruleId: rule.id,
1177
+ ruleName: name,
1178
+ action: failMode === "closed" ? "block" : "log",
1179
+ detector: "detector_error",
1180
+ category: "error.detector_unavailable",
1181
+ score: 0,
1182
+ count: 1,
1183
+ values: [],
1184
+ fresh: true,
1185
+ error: o.failed.slice(0, 300),
1186
+ failMode
1187
+ });
1188
+ if (failMode === "closed" && o.bySegment.size === 0) action = "block";
1189
+ else if (o.bySegment.size === 0) continue;
1190
+ }
1191
+ const historyOnly = (action === "block" || action === "approve") && o.bySegment.size > 0 && [
1192
+ ...o.bySegment.keys()
1193
+ ].every((i) => segments[i]?.fresh === false);
1194
+ if (historyOnly) {
1195
+ action = "redact";
1196
+ for (const [i, findings] of o.bySegment) {
1197
+ const entry = redactBySegment.get(i) ?? {
1198
+ findings: [],
1199
+ restorable: false
1200
+ };
1201
+ entry.findings.push(...findings);
1202
+ redactBySegment.set(i, entry);
1203
+ }
1204
+ }
1205
+ if (ACTION_SEVERITY[action] > ACTION_SEVERITY[result.action]) {
1206
+ result.action = action;
1207
+ if (action === "block" || action === "approve") {
1208
+ result.decisive = {
1209
+ id: rule.id,
1210
+ name,
1211
+ message: rule.options?.message || (o.failed && o.bySegment.size === 0 ? `${stageMessage} (a required check could not run)` : action === "approve" ? options.approvalMessage ?? DEFAULT_APPROVAL_MESSAGE : stageMessage)
1212
+ };
1213
+ }
1214
+ }
1215
+ if (o.bySegment.size === 0 && rule.detectors.length === 0) {
1216
+ result.hits.push({
1217
+ ruleId: rule.id,
1218
+ ruleName: name,
1219
+ action,
1220
+ detector: "scope",
1221
+ category: "scope.match",
1222
+ score: 1,
1223
+ count: 1,
1224
+ values: [],
1225
+ fresh: true
1226
+ });
1227
+ continue;
1228
+ }
1229
+ const grouped = /* @__PURE__ */ new Map();
1230
+ for (const [i, findings] of o.bySegment) {
1231
+ for (const f of findings) {
1232
+ const hit = grouped.get(f.category) ?? {
1233
+ ruleId: rule.id,
1234
+ ruleName: name,
1235
+ action,
1236
+ detector: f.detector,
1237
+ category: f.category,
1238
+ score: 0,
1239
+ count: 0,
1240
+ values: [],
1241
+ fresh: false
1242
+ };
1243
+ hit.count++;
1244
+ hit.score = Math.max(hit.score, f.score);
1245
+ if (f.value && !hit.values.includes(f.value) && hit.values.length < 20) hit.values.push(f.value);
1246
+ if (segments[i]?.fresh !== false) hit.fresh = true;
1247
+ grouped.set(f.category, hit);
1248
+ }
1249
+ if (action === "redact" && !historyOnly) {
1250
+ const entry = redactBySegment.get(i) ?? {
1251
+ findings: [],
1252
+ restorable: false
1253
+ };
1254
+ entry.findings.push(...findings);
1255
+ entry.restorable ||= restorableStage && rule.options?.restore !== false;
1256
+ redactBySegment.set(i, entry);
1257
+ }
1258
+ }
1259
+ result.hits.push(...grouped.values());
1260
+ }
1261
+ if (result.action !== "block" && result.action !== "approve") {
1262
+ for (const [i, { findings, restorable }] of redactBySegment) {
1263
+ const original = segments[i]?.text ?? "";
1264
+ const { text, count } = applyRedactions(original, findings, vault, restorable);
1265
+ if (count > 0 && text !== original) {
1266
+ result.segments[i] = text;
1267
+ result.redactions += count;
1268
+ result.changed = true;
1269
+ }
1270
+ }
1271
+ }
1272
+ result.latencyMs = performance.now() - started;
1273
+ return result;
1274
+ }
1275
+ __name(scan, "scan");
1276
+
1277
+ // src/guardrails/segments.ts
1278
+ var MAX_SLOTS = 2e3;
1279
+ function sourceOf(role) {
1280
+ if (role === "system" || role === "developer") return "system";
1281
+ if (role === "assistant") return "assistant";
1282
+ if (role === "tool" || role === "function") return "tool";
1283
+ return "user";
1284
+ }
1285
+ __name(sourceOf, "sourceOf");
1286
+ function freshFrom(messages) {
1287
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i]?.role === "assistant") return i + 1;
1288
+ return 0;
1289
+ }
1290
+ __name(freshFrom, "freshFrom");
1291
+ function jsonSlots(root, source, fresh, replaceRoot) {
1292
+ const out = [];
1293
+ const walk = /* @__PURE__ */ __name((value, set, depth) => {
1294
+ if (out.length >= MAX_SLOTS || depth > 10) return;
1295
+ if (typeof value === "string") {
1296
+ if (value) out.push({
1297
+ segment: {
1298
+ text: value,
1299
+ source,
1300
+ fresh
1301
+ },
1302
+ set
1303
+ });
1304
+ return;
1305
+ }
1306
+ if (Array.isArray(value)) {
1307
+ for (let i = 0; i < value.length; i++) {
1308
+ walk(value[i], (t) => {
1309
+ value[i] = t;
1310
+ }, depth + 1);
1311
+ }
1312
+ return;
1313
+ }
1314
+ if (value && typeof value === "object") {
1315
+ const o = value;
1316
+ for (const k of Object.keys(o)) walk(o[k], (t) => {
1317
+ o[k] = t;
1318
+ }, depth + 1);
1319
+ }
1320
+ }, "walk");
1321
+ walk(root, replaceRoot ?? (() => void 0), 0);
1322
+ return out;
1323
+ }
1324
+ __name(jsonSlots, "jsonSlots");
1325
+ function jsonStringSlot(holder, key, source, fresh) {
1326
+ const v = holder[key];
1327
+ if (typeof v !== "string" || !v) return [];
1328
+ return [
1329
+ {
1330
+ segment: {
1331
+ text: v,
1332
+ source,
1333
+ fresh
1334
+ },
1335
+ set: /* @__PURE__ */ __name((t) => {
1336
+ holder[key] = t;
1337
+ }, "set"),
1338
+ json: true
1339
+ }
1340
+ ];
1341
+ }
1342
+ __name(jsonStringSlot, "jsonStringSlot");
1343
+ function contentSlots(holder, source, fresh) {
1344
+ const content = holder.content;
1345
+ if (typeof content === "string") {
1346
+ return content ? [
1347
+ {
1348
+ segment: {
1349
+ text: content,
1350
+ source,
1351
+ fresh
1352
+ },
1353
+ set: /* @__PURE__ */ __name((t) => {
1354
+ holder.content = t;
1355
+ }, "set")
1356
+ }
1357
+ ] : [];
1358
+ }
1359
+ if (!Array.isArray(content)) return [];
1360
+ const out = [];
1361
+ for (const part of content) {
1362
+ if (!part || typeof part !== "object") continue;
1363
+ const p = part;
1364
+ if ((p.type === "text" || p.type === "input_text" || p.type === "output_text") && typeof p.text === "string") {
1365
+ out.push({
1366
+ segment: {
1367
+ text: p.text,
1368
+ source,
1369
+ fresh
1370
+ },
1371
+ set: /* @__PURE__ */ __name((t) => {
1372
+ p.text = t;
1373
+ }, "set")
1374
+ });
1375
+ } else if (p.type === "tool_result") {
1376
+ out.push(...contentSlots(p, "tool", fresh));
1377
+ } else if (p.type === "tool_use" && p.input && typeof p.input === "object") {
1378
+ out.push(...jsonSlots(p.input, "assistant", fresh));
1379
+ }
1380
+ }
1381
+ return out;
1382
+ }
1383
+ __name(contentSlots, "contentSlots");
1384
+ function requestSlots(api, body) {
1385
+ const out = [];
1386
+ if (api === "anthropic") {
1387
+ if (typeof body.system === "string" && body.system) {
1388
+ out.push({
1389
+ segment: {
1390
+ text: body.system,
1391
+ source: "system",
1392
+ fresh: true
1393
+ },
1394
+ set: /* @__PURE__ */ __name((t) => {
1395
+ body.system = t;
1396
+ }, "set")
1397
+ });
1398
+ } else if (Array.isArray(body.system)) {
1399
+ out.push(...contentSlots({
1400
+ content: body.system
1401
+ }, "system", true));
1402
+ }
1403
+ }
1404
+ if (Array.isArray(body.messages)) {
1405
+ const messages = body.messages;
1406
+ const from = freshFrom(messages);
1407
+ messages.forEach((m, i) => {
1408
+ if (!m || typeof m !== "object") return;
1409
+ const fresh = i >= from || m.role === "system";
1410
+ const source = sourceOf(m.role);
1411
+ out.push(...contentSlots(m, source, fresh));
1412
+ if (Array.isArray(m.tool_calls)) {
1413
+ for (const call of m.tool_calls) if (call?.function) out.push(...jsonStringSlot(call.function, "arguments", "assistant", fresh));
1414
+ }
1415
+ });
1416
+ }
1417
+ if (typeof body.input === "string" || Array.isArray(body.input)) {
1418
+ const holder = body;
1419
+ if (typeof holder.input === "string") {
1420
+ out.push({
1421
+ segment: {
1422
+ text: holder.input,
1423
+ source: "user",
1424
+ fresh: true
1425
+ },
1426
+ set: /* @__PURE__ */ __name((t) => {
1427
+ holder.input = t;
1428
+ }, "set")
1429
+ });
1430
+ } else {
1431
+ const list = holder.input;
1432
+ list.forEach((v, i) => {
1433
+ if (typeof v === "string" && v) out.push({
1434
+ segment: {
1435
+ text: v,
1436
+ source: "user",
1437
+ fresh: true
1438
+ },
1439
+ set: /* @__PURE__ */ __name((t) => {
1440
+ list[i] = t;
1441
+ }, "set")
1442
+ });
1443
+ });
1444
+ }
1445
+ }
1446
+ return out.slice(0, MAX_SLOTS);
1447
+ }
1448
+ __name(requestSlots, "requestSlots");
1449
+ function responseSlots(api, body) {
1450
+ if (!body || typeof body !== "object") return [];
1451
+ const b = body;
1452
+ const out = [];
1453
+ if (api === "anthropic") {
1454
+ if (Array.isArray(b.content)) out.push(...contentSlots(b, "assistant", true));
1455
+ return out;
1456
+ }
1457
+ for (const choice of Array.isArray(b.choices) ? b.choices : []) {
1458
+ const msg = choice?.message;
1459
+ if (!msg || typeof msg !== "object") continue;
1460
+ out.push(...contentSlots(msg, "assistant", true));
1461
+ if (Array.isArray(msg.tool_calls)) {
1462
+ for (const call of msg.tool_calls) if (call?.function) out.push(...jsonStringSlot(call.function, "arguments", "assistant", true));
1463
+ }
1464
+ }
1465
+ return out;
1466
+ }
1467
+ __name(responseSlots, "responseSlots");
1468
+ function refuseResponse(api, body, message) {
1469
+ if (!body || typeof body !== "object") return body;
1470
+ const b = body;
1471
+ if (api === "anthropic") {
1472
+ return {
1473
+ ...b,
1474
+ content: [
1475
+ {
1476
+ type: "text",
1477
+ text: message
1478
+ }
1479
+ ],
1480
+ stop_reason: "refusal"
1481
+ };
1482
+ }
1483
+ return {
1484
+ ...b,
1485
+ choices: (Array.isArray(b.choices) && b.choices.length > 0 ? b.choices : [
1486
+ {
1487
+ index: 0
1488
+ }
1489
+ ]).map((c) => ({
1490
+ ...c,
1491
+ message: {
1492
+ role: "assistant",
1493
+ content: message
1494
+ },
1495
+ finish_reason: "content_filter"
1496
+ }))
1497
+ };
1498
+ }
1499
+ __name(refuseResponse, "refuseResponse");
1500
+ function toolResultSlots(result) {
1501
+ const out = [];
1502
+ if (Array.isArray(result.content)) {
1503
+ for (const item of result.content) {
1504
+ if (!item || typeof item !== "object") continue;
1505
+ if (item.type === "text" && typeof item.text === "string") {
1506
+ out.push({
1507
+ segment: {
1508
+ text: item.text,
1509
+ source: "tool",
1510
+ fresh: true
1511
+ },
1512
+ set: /* @__PURE__ */ __name((t) => {
1513
+ item.text = t;
1514
+ }, "set")
1515
+ });
1516
+ } else if (item.type === "resource" && item.resource && typeof item.resource.text === "string") {
1517
+ out.push({
1518
+ segment: {
1519
+ text: item.resource.text,
1520
+ source: "tool",
1521
+ fresh: true
1522
+ },
1523
+ set: /* @__PURE__ */ __name((t) => {
1524
+ item.resource.text = t;
1525
+ }, "set")
1526
+ });
1527
+ }
1528
+ }
1529
+ }
1530
+ if (result.structuredContent && typeof result.structuredContent === "object") out.push(...jsonSlots(result.structuredContent, "tool", true));
1531
+ return out.slice(0, MAX_SLOTS);
1532
+ }
1533
+ __name(toolResultSlots, "toolResultSlots");
1534
+
1535
+ // src/guardrails/stream-guard.ts
1536
+ var Channel = class Channel2 {
1537
+ static {
1538
+ __name(this, "Channel");
1539
+ }
1540
+ json;
1541
+ pending = "";
1542
+ constructor(json) {
1543
+ this.json = json;
1544
+ }
1545
+ };
1546
+ var StreamGuard = class {
1547
+ static {
1548
+ __name(this, "StreamGuard");
1549
+ }
1550
+ opts;
1551
+ buffer = "";
1552
+ decoder = new TextDecoder();
1553
+ channels = /* @__PURE__ */ new Map();
1554
+ window;
1555
+ holdback;
1556
+ blocked;
1557
+ template;
1558
+ openBlocks = /* @__PURE__ */ new Set();
1559
+ maxBlock = -1;
1560
+ done = false;
1561
+ constructor(opts) {
1562
+ this.opts = opts;
1563
+ this.window = opts.window ?? 256;
1564
+ this.holdback = opts.holdback ?? 64;
1565
+ }
1566
+ /** Whether the guard changes anything (else the caller can pass bytes straight through). */
1567
+ get active() {
1568
+ return !!this.opts.scan || this.opts.restore && this.opts.vault.restorableCount > 0;
1569
+ }
1570
+ async push(chunk) {
1571
+ this.buffer += typeof chunk === "string" ? chunk : this.decoder.decode(chunk, {
1572
+ stream: true
1573
+ });
1574
+ let out = "";
1575
+ for (; ; ) {
1576
+ const m = /\r?\n\r?\n/.exec(this.buffer);
1577
+ if (!m) break;
1578
+ const raw = this.buffer.slice(0, m.index + m[0].length);
1579
+ this.buffer = this.buffer.slice(m.index + m[0].length);
1580
+ out += await this.frame(parseFrame(raw));
1581
+ }
1582
+ return out;
1583
+ }
1584
+ async end() {
1585
+ let out = "";
1586
+ if (this.buffer.trim()) out += await this.frame(parseFrame(`${this.buffer}
1587
+
1588
+ `));
1589
+ this.buffer = "";
1590
+ if (!this.blocked && !this.done) out += await this.flushAll();
1591
+ return out;
1592
+ }
1593
+ async frame(f) {
1594
+ if (this.blocked) return "";
1595
+ if (this.opts.api === "openai") return this.openaiFrame(f);
1596
+ return this.anthropicFrame(f);
1597
+ }
1598
+ // ---- OpenAI chat-completion chunks ---------------------------------------------------------
1599
+ async openaiFrame(f) {
1600
+ if (!f.data) return f.raw;
1601
+ if (f.data.trim() === "[DONE]") {
1602
+ const tail = await this.flushAll();
1603
+ this.done = true;
1604
+ return tail + f.raw;
1605
+ }
1606
+ let chunk;
1607
+ try {
1608
+ chunk = JSON.parse(f.data);
1609
+ } catch {
1610
+ return f.raw;
1611
+ }
1612
+ this.template ??= {
1613
+ id: chunk.id,
1614
+ object: chunk.object,
1615
+ created: chunk.created,
1616
+ model: chunk.model
1617
+ };
1618
+ if (!Array.isArray(chunk.choices) || chunk.choices.length === 0) return f.raw;
1619
+ let prefix = "";
1620
+ let keep = false;
1621
+ for (const choice of chunk.choices) {
1622
+ const delta = choice.delta ?? {};
1623
+ const ci = choice.index ?? 0;
1624
+ if (typeof delta.content === "string" && delta.content) {
1625
+ const r = await this.feed(`c:${ci}`, delta.content, false);
1626
+ if (this.blocked) return prefix + this.openaiBlock(ci);
1627
+ delta.content = r;
1628
+ if (r) keep = true;
1629
+ }
1630
+ if (Array.isArray(delta.tool_calls)) {
1631
+ for (const call of delta.tool_calls) {
1632
+ const fn = call.function;
1633
+ if (fn && typeof fn.arguments === "string" && fn.arguments) {
1634
+ const r = await this.feed(`t:${ci}:${call.index ?? 0}`, fn.arguments, true);
1635
+ if (this.blocked) return prefix + this.openaiBlock(ci);
1636
+ fn.arguments = r;
1637
+ }
1638
+ if (call.id || fn?.name || fn?.arguments) keep = true;
1639
+ }
1640
+ }
1641
+ if (delta.role || delta.reasoning_content || delta.refusal) keep = true;
1642
+ if (choice.finish_reason) {
1643
+ prefix += await this.flushChoice(ci);
1644
+ if (this.blocked) return prefix + this.openaiBlock(ci);
1645
+ keep = true;
1646
+ }
1647
+ }
1648
+ if (chunk.usage) keep = true;
1649
+ return keep ? `${prefix}data: ${JSON.stringify(chunk)}
1650
+
1651
+ ` : prefix;
1652
+ }
1653
+ openaiChunk(ci, delta, finish = null) {
1654
+ return `data: ${JSON.stringify({
1655
+ ...this.template ?? {
1656
+ object: "chat.completion.chunk"
1657
+ },
1658
+ choices: [
1659
+ {
1660
+ index: ci,
1661
+ delta,
1662
+ finish_reason: finish
1663
+ }
1664
+ ]
1665
+ })}
1666
+
1667
+ `;
1668
+ }
1669
+ openaiBlock(ci) {
1670
+ const msg = this.blocked?.message ?? "";
1671
+ this.done = true;
1672
+ return `${this.openaiChunk(ci, {
1673
+ content: `
1674
+
1675
+ ${msg}`
1676
+ })}${this.openaiChunk(ci, {}, "content_filter")}data: [DONE]
1677
+
1678
+ `;
1679
+ }
1680
+ async flushChoice(ci) {
1681
+ let out = "";
1682
+ for (const [key, ch] of this.channels) {
1683
+ if (!key.startsWith(`c:${ci}`) && !key.startsWith(`t:${ci}:`)) continue;
1684
+ const text = await this.release(key, ch, true);
1685
+ if (this.blocked) return out;
1686
+ if (!text) continue;
1687
+ out += key.startsWith("c:") ? this.openaiChunk(ci, {
1688
+ content: text
1689
+ }) : this.openaiChunk(ci, {
1690
+ tool_calls: [
1691
+ {
1692
+ index: Number(key.split(":")[2]),
1693
+ function: {
1694
+ arguments: text
1695
+ }
1696
+ }
1697
+ ]
1698
+ });
1699
+ }
1700
+ return out;
1701
+ }
1702
+ // ---- Anthropic Messages events -------------------------------------------------------------
1703
+ async anthropicFrame(f) {
1704
+ if (!f.data) return f.raw;
1705
+ let ev;
1706
+ try {
1707
+ ev = JSON.parse(f.data);
1708
+ } catch {
1709
+ return f.raw;
1710
+ }
1711
+ const type = ev.type ?? f.event;
1712
+ if (type === "content_block_start") {
1713
+ this.openBlocks.add(ev.index);
1714
+ this.maxBlock = Math.max(this.maxBlock, ev.index);
1715
+ return f.raw;
1716
+ }
1717
+ if (type === "content_block_delta") {
1718
+ const d = ev.delta ?? {};
1719
+ if (d.type === "text_delta" && typeof d.text === "string") {
1720
+ const r = await this.feed(`b:${ev.index}`, d.text, false);
1721
+ if (this.blocked) return this.anthropicBlock(ev.index);
1722
+ if (!r) return "";
1723
+ d.text = r;
1724
+ return sseEvent("content_block_delta", ev);
1725
+ }
1726
+ if (d.type === "input_json_delta" && typeof d.partial_json === "string") {
1727
+ const r = await this.feed(`b:${ev.index}`, d.partial_json, true);
1728
+ if (this.blocked) return this.anthropicBlock(ev.index);
1729
+ if (!r) return "";
1730
+ d.partial_json = r;
1731
+ return sseEvent("content_block_delta", ev);
1732
+ }
1733
+ return f.raw;
1734
+ }
1735
+ if (type === "content_block_stop") {
1736
+ const pre = await this.flushBlock(ev.index);
1737
+ if (this.blocked) return pre + this.anthropicBlock(ev.index);
1738
+ this.openBlocks.delete(ev.index);
1739
+ return pre + f.raw;
1740
+ }
1741
+ if (type === "message_delta" || type === "message_stop") {
1742
+ let pre = "";
1743
+ for (const i of [
1744
+ ...this.openBlocks
1745
+ ]) {
1746
+ pre += await this.flushBlock(i);
1747
+ if (this.blocked) return pre + this.anthropicBlock(i);
1748
+ }
1749
+ if (type === "message_stop") this.done = true;
1750
+ return pre + f.raw;
1751
+ }
1752
+ return f.raw;
1753
+ }
1754
+ async flushBlock(index) {
1755
+ const key = `b:${index}`;
1756
+ const ch = this.channels.get(key);
1757
+ if (!ch) return "";
1758
+ const text = await this.release(key, ch, true);
1759
+ if (this.blocked || !text) return "";
1760
+ return sseEvent("content_block_delta", {
1761
+ type: "content_block_delta",
1762
+ index,
1763
+ delta: ch.json ? {
1764
+ type: "input_json_delta",
1765
+ partial_json: text
1766
+ } : {
1767
+ type: "text_delta",
1768
+ text
1769
+ }
1770
+ });
1771
+ }
1772
+ anthropicBlock(index) {
1773
+ const msg = this.blocked?.message ?? "";
1774
+ this.done = true;
1775
+ let out = "";
1776
+ let i = index;
1777
+ const ch = this.channels.get(`b:${index}`);
1778
+ if (!this.openBlocks.has(index) || ch?.json) {
1779
+ if (this.openBlocks.has(index)) out += sseEvent("content_block_stop", {
1780
+ type: "content_block_stop",
1781
+ index
1782
+ });
1783
+ i = this.maxBlock + 1;
1784
+ out += sseEvent("content_block_start", {
1785
+ type: "content_block_start",
1786
+ index: i,
1787
+ content_block: {
1788
+ type: "text",
1789
+ text: ""
1790
+ }
1791
+ });
1792
+ }
1793
+ out += sseEvent("content_block_delta", {
1794
+ type: "content_block_delta",
1795
+ index: i,
1796
+ delta: {
1797
+ type: "text_delta",
1798
+ text: `
1799
+
1800
+ ${msg}`
1801
+ }
1802
+ });
1803
+ out += sseEvent("content_block_stop", {
1804
+ type: "content_block_stop",
1805
+ index: i
1806
+ });
1807
+ out += sseEvent("message_delta", {
1808
+ type: "message_delta",
1809
+ delta: {
1810
+ stop_reason: "refusal",
1811
+ stop_sequence: null
1812
+ },
1813
+ usage: {
1814
+ output_tokens: 0
1815
+ }
1816
+ });
1817
+ out += sseEvent("message_stop", {
1818
+ type: "message_stop"
1819
+ });
1820
+ return out;
1821
+ }
1822
+ // ---- windows -------------------------------------------------------------------------------
1823
+ async flushAll() {
1824
+ if (this.opts.api === "openai") {
1825
+ const choices = new Set([
1826
+ ...this.channels.keys()
1827
+ ].map((k) => Number(k.split(":")[1])));
1828
+ let out2 = "";
1829
+ for (const ci of choices) {
1830
+ out2 += await this.flushChoice(ci);
1831
+ if (this.blocked) return out2 + this.openaiBlock(ci);
1832
+ }
1833
+ return out2;
1834
+ }
1835
+ let out = "";
1836
+ for (const i of [
1837
+ ...this.openBlocks
1838
+ ]) {
1839
+ out += await this.flushBlock(i);
1840
+ if (this.blocked) return out + this.anthropicBlock(i);
1841
+ }
1842
+ return out;
1843
+ }
1844
+ async feed(key, text, json) {
1845
+ let ch = this.channels.get(key);
1846
+ if (!ch) {
1847
+ ch = new Channel(json);
1848
+ this.channels.set(key, ch);
1849
+ }
1850
+ ch.pending += text;
1851
+ if (ch.pending.length < this.window + this.holdback) return "";
1852
+ return this.release(key, ch, false);
1853
+ }
1854
+ /** Releases a window (everything when `all`), scanned and restored. */
1855
+ async release(key, ch, all) {
1856
+ if (!ch.pending) return "";
1857
+ let cut = all ? ch.pending.length : ch.pending.length - this.holdback;
1858
+ if (!all) {
1859
+ const spans = resolveOverlaps(this.opts.locate?.(ch.pending) ?? []);
1860
+ const straddling = spans.find((f) => f.start < cut && f.end > cut);
1861
+ if (straddling) cut = straddling.end < ch.pending.length ? straddling.end : straddling.start;
1862
+ const head = ch.pending.slice(0, cut);
1863
+ cut -= this.opts.vault.pendingPrefix(head);
1864
+ if (cut <= 0) return "";
1865
+ }
1866
+ let text = ch.pending.slice(0, cut);
1867
+ ch.pending = ch.pending.slice(cut);
1868
+ if (this.opts.scan) {
1869
+ const verdict = await this.opts.scan(text, key);
1870
+ if (verdict.block) {
1871
+ this.blocked = verdict.block;
1872
+ return "";
1873
+ }
1874
+ text = verdict.text;
1875
+ }
1876
+ return this.opts.restore ? this.opts.vault.restore(text, ch.json) : text;
1877
+ }
1878
+ };
1879
+ function parseFrame(raw) {
1880
+ let event;
1881
+ const data = [];
1882
+ for (const line of raw.split(/\r?\n/)) {
1883
+ if (line.startsWith("event:")) event = line.slice(6).trim();
1884
+ else if (line.startsWith("data:")) data.push(line.slice(5).replace(/^ /, ""));
1885
+ }
1886
+ return {
1887
+ event,
1888
+ data: data.join("\n"),
1889
+ raw
1890
+ };
1891
+ }
1892
+ __name(parseFrame, "parseFrame");
1893
+ function sseEvent(event, data) {
1894
+ return `event: ${event}
1895
+ data: ${JSON.stringify(data)}
1896
+
1897
+ `;
1898
+ }
1899
+ __name(sseEvent, "sseEvent");
1900
+
1901
+ // src/guardrails/agent.ts
1902
+ var InMemoryVaultStore = class {
1903
+ static {
1904
+ __name(this, "InMemoryVaultStore");
1905
+ }
1906
+ maxThreads;
1907
+ vaults = /* @__PURE__ */ new Map();
1908
+ constructor(maxThreads = 1e3) {
1909
+ this.maxThreads = maxThreads;
1910
+ }
1911
+ load(threadId) {
1912
+ const vault = this.vaults.get(threadId);
1913
+ if (vault !== void 0) {
1914
+ this.vaults.delete(threadId);
1915
+ this.vaults.set(threadId, vault);
1916
+ }
1917
+ return vault;
1918
+ }
1919
+ save(threadId, vault) {
1920
+ this.vaults.delete(threadId);
1921
+ this.vaults.set(threadId, vault);
1922
+ while (this.vaults.size > this.maxThreads) {
1923
+ const oldest = this.vaults.keys().next().value;
1924
+ if (oldest === void 0) break;
1925
+ this.vaults.delete(oldest);
1926
+ }
1927
+ }
1928
+ };
1929
+ var GuardrailBlockedError = class extends Error {
1930
+ static {
1931
+ __name(this, "GuardrailBlockedError");
1932
+ }
1933
+ stage;
1934
+ userMessage;
1935
+ rule;
1936
+ constructor(stage, userMessage, rule) {
1937
+ super(userMessage), this.stage = stage, this.userMessage = userMessage, this.rule = rule;
1938
+ this.name = "GuardrailBlockedError";
1939
+ }
1940
+ };
1941
+ var ALL_CONTENT = [
1942
+ "llm_request",
1943
+ "tool_args",
1944
+ "tool_result",
1945
+ "llm_response"
1946
+ ];
1947
+ var RESTORABLE_STAGES = [
1948
+ "llm_request",
1949
+ "tool_result"
1950
+ ];
1951
+ var PII_STAGES = [
1952
+ "llm_request",
1953
+ "tool_result",
1954
+ "llm_response"
1955
+ ];
1956
+ function shorthandRules(options) {
1957
+ const rules = [];
1958
+ if (options.pii !== void 0) {
1959
+ const pii = typeof options.pii === "string" ? {
1960
+ action: options.pii
1961
+ } : options.pii;
1962
+ rules.push({
1963
+ id: "pii",
1964
+ name: "PII",
1965
+ stages: pii.stages ?? PII_STAGES,
1966
+ detectors: [
1967
+ {
1968
+ kind: "pii",
1969
+ ...pii.types !== void 0 ? {
1970
+ types: pii.types
1971
+ } : {}
1972
+ }
1973
+ ],
1974
+ action: pii.action,
1975
+ options: {
1976
+ restore: pii.restore ?? true
1977
+ }
1978
+ });
1979
+ }
1980
+ if (options.secrets !== void 0) {
1981
+ const secrets = typeof options.secrets === "string" ? {
1982
+ action: options.secrets
1983
+ } : options.secrets;
1984
+ rules.push({
1985
+ id: "secrets",
1986
+ name: "Secrets",
1987
+ stages: secrets.stages ?? ALL_CONTENT,
1988
+ detectors: [
1989
+ {
1990
+ kind: "secrets",
1991
+ ...secrets.types !== void 0 ? {
1992
+ types: secrets.types
1993
+ } : {}
1994
+ }
1995
+ ],
1996
+ action: secrets.action,
1997
+ // A secret sent to a model is not handed back: the reader should not see it echoed either.
1998
+ options: {
1999
+ restore: false
2000
+ }
2001
+ });
2002
+ }
2003
+ if (options.injection !== void 0) {
2004
+ const injection = typeof options.injection === "string" ? {
2005
+ action: options.injection
2006
+ } : options.injection;
2007
+ rules.push({
2008
+ id: "injection",
2009
+ name: "Prompt injection",
2010
+ // The prompt (a jailbreak typed by the user) and tool results (indirect injection).
2011
+ stages: injection.stages ?? [
2012
+ "llm_request",
2013
+ "tool_result"
2014
+ ],
2015
+ detectors: [
2016
+ {
2017
+ kind: "injection",
2018
+ ...injection.threshold !== void 0 ? {
2019
+ threshold: injection.threshold
2020
+ } : {}
2021
+ }
2022
+ ],
2023
+ action: injection.action ?? "block",
2024
+ // What the user typed and what tools returned. Not the system prompt — the app's own text,
2025
+ // which legitimately carries role tags and imperatives — nor the model's earlier answers.
2026
+ match: {
2027
+ sources: [
2028
+ "user",
2029
+ "tool"
2030
+ ]
2031
+ }
2032
+ });
2033
+ }
2034
+ if (options.toolPoisoning !== void 0 && options.toolPoisoning !== false) {
2035
+ const poisoning = options.toolPoisoning === true ? {} : options.toolPoisoning;
2036
+ rules.push({
2037
+ id: "tool_poisoning",
2038
+ name: "Tool poisoning",
2039
+ stages: [
2040
+ "tool_description"
2041
+ ],
2042
+ detectors: [
2043
+ {
2044
+ kind: "tool_poisoning",
2045
+ ...poisoning.threshold !== void 0 ? {
2046
+ threshold: poisoning.threshold
2047
+ } : {}
2048
+ }
2049
+ ],
2050
+ action: poisoning.action ?? "block"
2051
+ });
2052
+ }
2053
+ return rules;
2054
+ }
2055
+ __name(shorthandRules, "shorthandRules");
2056
+ var isRefusal = /* @__PURE__ */ __name((action) => action === "block" || action === "approve", "isRefusal");
2057
+ function sourceOf2(role) {
2058
+ return role === "system" ? "system" : role === "assistant" ? "assistant" : "user";
2059
+ }
2060
+ __name(sourceOf2, "sourceOf");
2061
+ function turnStart(messages) {
2062
+ for (let i = messages.length - 1; i >= 0; i--) {
2063
+ const message = messages[i];
2064
+ if (message?.role === "user" && message.content) return i;
2065
+ }
2066
+ return 0;
2067
+ }
2068
+ __name(turnStart, "turnStart");
2069
+ function cloneJson(value) {
2070
+ return value === void 0 ? value : structuredClone(value);
2071
+ }
2072
+ __name(cloneJson, "cloneJson");
2073
+ function writeBack(slots, result) {
2074
+ if (!result.changed) return;
2075
+ slots.forEach((slot, i) => {
2076
+ const text = result.segments[i];
2077
+ if (text !== void 0 && text !== slot.segment.text) slot.set(text);
2078
+ });
2079
+ }
2080
+ __name(writeBack, "writeBack");
2081
+ var Guardrails = class {
2082
+ static {
2083
+ __name(this, "Guardrails");
2084
+ }
2085
+ options;
2086
+ /** Scans every prompt; redacts in place, withholds tool results, throws on a blocked prompt. */
2087
+ input;
2088
+ /** Scans every answer and the tool calls it asks for; redacts, restores placeholders, or rejects. */
2089
+ output;
2090
+ shorthand;
2091
+ vaults;
2092
+ /** Per thread: hits already reported this turn, so re-scans of the same content stay quiet. */
2093
+ reported = /* @__PURE__ */ new Map();
2094
+ constructor(options = {}) {
2095
+ this.options = options;
2096
+ this.shorthand = shorthandRules(options);
2097
+ this.vaults = options.vaults ?? new InMemoryVaultStore();
2098
+ const name = options.name ?? "guardrails";
2099
+ this.input = {
2100
+ name,
2101
+ process: /* @__PURE__ */ __name((prompt, ctx) => this.processInput(prompt, ctx), "process")
2102
+ };
2103
+ const incremental = options.incremental === false ? void 0 : {
2104
+ lookbackChars: options.incremental?.lookbackChars ?? 256
2105
+ };
2106
+ this.output = {
2107
+ name,
2108
+ ...incremental !== void 0 ? {
2109
+ incremental
2110
+ } : {},
2111
+ process: /* @__PURE__ */ __name((answer, ctx) => this.processOutput(answer, ctx), "process")
2112
+ };
2113
+ }
2114
+ /** Every rule in force for `ctx`: the shorthands, then {@link GuardrailsOptions.rules}. */
2115
+ async rulesFor(ctx) {
2116
+ const extra = this.options.rules;
2117
+ const resolved = typeof extra === "function" ? await extra(ctx) : extra ?? [];
2118
+ return [
2119
+ ...this.shorthand,
2120
+ ...resolved
2121
+ ];
2122
+ }
2123
+ /**
2124
+ * Scans arbitrary segments at one stage with the configured rules, reporting through `onEvent`.
2125
+ * What the processors use; also the entry point for traffic that never meets the loop.
2126
+ */
2127
+ async scan(ctx, segments, vault = new Vault()) {
2128
+ const rules = await this.rulesFor(ctx);
2129
+ const result = await scan(rules, ctx, segments, vault, {
2130
+ ...this.options.messages !== void 0 ? {
2131
+ messages: this.options.messages
2132
+ } : {},
2133
+ // A tool result is the app's own data: a value redacted out of it is handed back to the
2134
+ // reader and to the next tool call like one the user typed. `restore: false` opts a rule out.
2135
+ restorableStages: RESTORABLE_STAGES
2136
+ });
2137
+ await this.report(ctx, result);
2138
+ return result;
2139
+ }
2140
+ /**
2141
+ * Screens one tool definition (name, title, description, and every description in its input
2142
+ * schema) for hidden instructions. `allowed: false` means a `block`/`approve` rule fired — leave
2143
+ * the tool out of the catalog. Shaped to be handed straight to an MCP importer's screen hook.
2144
+ */
2145
+ async screenTool(tool) {
2146
+ const ctx = {
2147
+ stage: "tool_description",
2148
+ tool: tool.name,
2149
+ ...tool.server !== void 0 ? {
2150
+ server: tool.server
2151
+ } : {}
2152
+ };
2153
+ const result = await this.scan(ctx, [
2154
+ {
2155
+ text: toolText(tool)
2156
+ }
2157
+ ]);
2158
+ if (isRefusal(result.action)) {
2159
+ const categories = [
2160
+ ...new Set(result.hits.map((h) => h.category))
2161
+ ].join(", ");
2162
+ return {
2163
+ allowed: false,
2164
+ reason: `${result.decisive?.message ?? "quarantined by guardrails"} (${categories})`
2165
+ };
2166
+ }
2167
+ return {
2168
+ allowed: true
2169
+ };
2170
+ }
2171
+ /**
2172
+ * Wraps a tool handler so its arguments are guarded where the loop cannot rewrite them: the
2173
+ * thread's placeholders are restored (the model only ever saw `[EMAIL_1]`, the tool needs the
2174
+ * address), then the `tool_args` rules run — `redact` rewrites what the tool receives, `block`
2175
+ * throws a {@link GuardrailBlockedError}, which the model reads as the tool's failure.
2176
+ */
2177
+ wrapTool(toolName, handler) {
2178
+ return {
2179
+ execute: /* @__PURE__ */ __name(async (input, ctx) => {
2180
+ const vault = await this.vaults.load(ctx.threadId) ?? new Vault();
2181
+ const box = {
2182
+ value: cloneJson(input)
2183
+ };
2184
+ const slots = jsonSlots(box.value, "assistant", true, (text) => {
2185
+ box.value = text;
2186
+ });
2187
+ for (const slot of slots) slot.set(vault.restore(slot.segment.text));
2188
+ const guardCtx = {
2189
+ stage: "tool_args",
2190
+ tool: toolName,
2191
+ threadId: ctx.threadId,
2192
+ actor: ctx.actor,
2193
+ ...ctx.agentName !== void 0 ? {
2194
+ agentName: ctx.agentName
2195
+ } : {}
2196
+ };
2197
+ const restoredSlots = jsonSlots(box.value, "assistant", true, (text) => {
2198
+ box.value = text;
2199
+ });
2200
+ const result = await this.scan(guardCtx, restoredSlots.map((s) => s.segment), vault);
2201
+ if (isRefusal(result.action)) {
2202
+ throw new GuardrailBlockedError("tool_args", result.decisive?.message ?? "blocked", result.decisive);
2203
+ }
2204
+ writeBack(restoredSlots, result);
2205
+ return handler.execute(box.value, ctx);
2206
+ }, "execute"),
2207
+ ...handler.isEnabled !== void 0 ? {
2208
+ isEnabled: /* @__PURE__ */ __name(() => handler.isEnabled?.call(handler) ?? true, "isEnabled")
2209
+ } : {},
2210
+ ...handler.canUse !== void 0 ? {
2211
+ canUse: /* @__PURE__ */ __name((actor) => handler.canUse?.call(handler, actor) ?? true, "canUse")
2212
+ } : {}
2213
+ };
2214
+ }
2215
+ contextOf(stage, ctx, tool) {
2216
+ return {
2217
+ stage,
2218
+ threadId: ctx.threadId,
2219
+ actor: ctx.actor,
2220
+ step: ctx.step,
2221
+ ...ctx.agentName !== void 0 ? {
2222
+ agentName: ctx.agentName
2223
+ } : {},
2224
+ ...tool !== void 0 ? {
2225
+ tool
2226
+ } : {}
2227
+ };
2228
+ }
2229
+ async processInput(prompt, pctx) {
2230
+ if (pctx.step === 0) {
2231
+ this.reported.delete(pctx.threadId);
2232
+ }
2233
+ const vault = await this.vaults.load(pctx.threadId) ?? new Vault();
2234
+ const out = {
2235
+ system: prompt.system,
2236
+ messages: cloneJson(prompt.messages)
2237
+ };
2238
+ const from = turnStart(out.messages);
2239
+ const slots = [];
2240
+ if (out.system) {
2241
+ slots.push({
2242
+ segment: {
2243
+ text: out.system,
2244
+ source: "system",
2245
+ fresh: true
2246
+ },
2247
+ set: /* @__PURE__ */ __name((text) => {
2248
+ out.system = text;
2249
+ }, "set")
2250
+ });
2251
+ }
2252
+ out.messages.forEach((message, i) => {
2253
+ const fresh = i >= from;
2254
+ if (message.content) {
2255
+ slots.push({
2256
+ segment: {
2257
+ text: message.content,
2258
+ source: sourceOf2(message.role),
2259
+ fresh
2260
+ },
2261
+ set: /* @__PURE__ */ __name((text) => {
2262
+ message.content = text;
2263
+ }, "set")
2264
+ });
2265
+ }
2266
+ for (const call of message.toolCalls ?? []) {
2267
+ slots.push(...jsonSlots(call.input, "assistant", fresh, (text) => {
2268
+ call.input = text;
2269
+ }));
2270
+ }
2271
+ });
2272
+ const request = await this.scan(this.contextOf("llm_request", pctx), slots.map((s) => s.segment), vault);
2273
+ if (isRefusal(request.action)) {
2274
+ await this.vaults.save(pctx.threadId, vault);
2275
+ throw new GuardrailBlockedError("llm_request", request.decisive?.message ?? "blocked", request.decisive);
2276
+ }
2277
+ writeBack(slots, request);
2278
+ for (const [i, message] of out.messages.entries()) {
2279
+ for (const result of message.toolResults ?? []) {
2280
+ await this.guardToolResult(result, i >= from, pctx, vault);
2281
+ }
2282
+ }
2283
+ await this.vaults.save(pctx.threadId, vault);
2284
+ return out;
2285
+ }
2286
+ async guardToolResult(result, fresh, pctx, vault) {
2287
+ const slots = jsonSlots(result.output, "tool", fresh, (text) => {
2288
+ result.output = text;
2289
+ });
2290
+ if (result.error) {
2291
+ slots.push({
2292
+ segment: {
2293
+ text: result.error,
2294
+ source: "tool",
2295
+ fresh
2296
+ },
2297
+ set: /* @__PURE__ */ __name((text) => {
2298
+ result.error = text;
2299
+ }, "set")
2300
+ });
2301
+ }
2302
+ if (slots.length === 0) return;
2303
+ const scanned = await this.scan(this.contextOf("tool_result", pctx, result.name), slots.map((s) => s.segment), vault);
2304
+ if (isRefusal(scanned.action)) {
2305
+ result.output = scanned.decisive?.message ?? "withheld";
2306
+ if (result.error !== void 0) result.error = result.output;
2307
+ return;
2308
+ }
2309
+ writeBack(slots, scanned);
2310
+ }
2311
+ async processOutput(answer, pctx) {
2312
+ for (const call of answer.toolCalls) {
2313
+ const slots = jsonSlots(cloneJson(call.input), "assistant", true);
2314
+ if (slots.length === 0) continue;
2315
+ const args = await this.scan(this.contextOf("tool_args", pctx, call.name), slots.map((s) => s.segment));
2316
+ if (isRefusal(args.action)) {
2317
+ return {
2318
+ action: "reject",
2319
+ reason: args.decisive?.message ?? "blocked"
2320
+ };
2321
+ }
2322
+ }
2323
+ if (!answer.text) return {
2324
+ action: "pass"
2325
+ };
2326
+ const response = await this.scan(this.contextOf("llm_response", pctx), [
2327
+ {
2328
+ text: answer.text,
2329
+ source: "assistant"
2330
+ }
2331
+ ], new Vault());
2332
+ if (isRefusal(response.action)) {
2333
+ return {
2334
+ action: "reject",
2335
+ reason: response.decisive?.message ?? "blocked"
2336
+ };
2337
+ }
2338
+ const vault = await this.vaults.load(pctx.threadId);
2339
+ const redacted = response.segments[0] ?? answer.text;
2340
+ const text = vault !== void 0 ? vault.restore(redacted) : redacted;
2341
+ return text === answer.text ? {
2342
+ action: "pass"
2343
+ } : {
2344
+ action: "replace",
2345
+ text
2346
+ };
2347
+ }
2348
+ async report(ctx, result) {
2349
+ const onEvent = this.options.onEvent;
2350
+ if (onEvent === void 0 || result.hits.length === 0) return;
2351
+ const seen = ctx.threadId !== void 0 ? this.reported.get(ctx.threadId) ?? this.reported.set(ctx.threadId, /* @__PURE__ */ new Set()).get(ctx.threadId) : void 0;
2352
+ const fingerprint = this.options.fingerprint;
2353
+ const hits = [];
2354
+ for (const { values, ...hit } of result.hits) {
2355
+ if (!hit.fresh) continue;
2356
+ const key = `${ctx.stage}\0${ctx.tool ?? ""}\0${hit.ruleId}\0${hit.category}\0${values.join("")}`;
2357
+ if (seen?.has(key)) continue;
2358
+ seen?.add(key);
2359
+ hits.push({
2360
+ ...hit,
2361
+ fingerprints: fingerprint !== void 0 ? values.map(fingerprint) : []
2362
+ });
2363
+ }
2364
+ if (hits.length === 0) return;
2365
+ if (this.reported.size > 1e3) {
2366
+ const oldest = this.reported.keys().next().value;
2367
+ if (oldest !== void 0) this.reported.delete(oldest);
2368
+ }
2369
+ try {
2370
+ await onEvent({
2371
+ stage: result.stage,
2372
+ action: result.action,
2373
+ ...result.decisive !== void 0 ? {
2374
+ decisive: result.decisive
2375
+ } : {},
2376
+ hits,
2377
+ redactions: result.redactions,
2378
+ latencyMs: result.latencyMs,
2379
+ context: ctx
2380
+ });
2381
+ } catch {
2382
+ }
2383
+ }
2384
+ };
2385
+ function createGuardrails(options = {}) {
2386
+ return new Guardrails(options);
2387
+ }
2388
+ __name(createGuardrails, "createGuardrails");
2389
+ function screenToolDefinition(tool, threshold = 0.5) {
2390
+ return scoreToolText(toolText(tool), threshold);
2391
+ }
2392
+ __name(screenToolDefinition, "screenToolDefinition");
2393
+ export {
2394
+ ACTION_SEVERITY,
2395
+ DEFAULT_APPROVAL_MESSAGE,
2396
+ DEFAULT_MESSAGES,
2397
+ GUARDRAIL_STAGES,
2398
+ GuardrailBlockedError,
2399
+ Guardrails,
2400
+ INJECTION_REPLACEMENT,
2401
+ INJECTION_SIGNALS,
2402
+ InMemoryVaultStore,
2403
+ PII_TYPES,
2404
+ SECRET_TYPES,
2405
+ StreamGuard,
2406
+ Vault,
2407
+ applyRedactions,
2408
+ cardBrand,
2409
+ cnpjValid,
2410
+ compileGuardPattern,
2411
+ cpfValid,
2412
+ createGuardrails,
2413
+ decodeTagChars,
2414
+ detectKeywords,
2415
+ detectPii,
2416
+ detectRegex,
2417
+ detectSecrets,
2418
+ entropy,
2419
+ fold,
2420
+ globToRegExp,
2421
+ ibanValid,
2422
+ isIPv4,
2423
+ jsonSlots,
2424
+ labelFor,
2425
+ luhnValid,
2426
+ matchesAny,
2427
+ orderRules,
2428
+ phoneValid,
2429
+ refuseResponse,
2430
+ requestSlots,
2431
+ resolveOverlaps,
2432
+ responseSlots,
2433
+ ruleApplies,
2434
+ runDetector,
2435
+ scan,
2436
+ scoreInjection,
2437
+ scoreToolText,
2438
+ screenToolDefinition,
2439
+ shorthandRules,
2440
+ spanLocator,
2441
+ ssnValid,
2442
+ toolResultSlots,
2443
+ toolText
2444
+ };
2445
+ //# sourceMappingURL=index.js.map