mcp-rubber-duck 1.8.0 → 1.9.3

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.
Files changed (120) hide show
  1. package/.github/workflows/semantic-release.yml +12 -1
  2. package/.releaserc.json +6 -1
  3. package/CHANGELOG.md +30 -0
  4. package/README.md +158 -1
  5. package/audit-ci.json +3 -1
  6. package/dist/config/config.d.ts +2 -0
  7. package/dist/config/config.d.ts.map +1 -1
  8. package/dist/config/config.js +144 -1
  9. package/dist/config/config.js.map +1 -1
  10. package/dist/config/types.d.ts +1084 -2
  11. package/dist/config/types.d.ts.map +1 -1
  12. package/dist/config/types.js +59 -0
  13. package/dist/config/types.js.map +1 -1
  14. package/dist/guardrails/context.d.ts +10 -0
  15. package/dist/guardrails/context.d.ts.map +1 -0
  16. package/dist/guardrails/context.js +35 -0
  17. package/dist/guardrails/context.js.map +1 -0
  18. package/dist/guardrails/errors.d.ts +26 -0
  19. package/dist/guardrails/errors.d.ts.map +1 -0
  20. package/dist/guardrails/errors.js +42 -0
  21. package/dist/guardrails/errors.js.map +1 -0
  22. package/dist/guardrails/index.d.ts +6 -0
  23. package/dist/guardrails/index.d.ts.map +1 -0
  24. package/dist/guardrails/index.js +11 -0
  25. package/dist/guardrails/index.js.map +1 -0
  26. package/dist/guardrails/plugins/base-plugin.d.ts +35 -0
  27. package/dist/guardrails/plugins/base-plugin.d.ts.map +1 -0
  28. package/dist/guardrails/plugins/base-plugin.js +70 -0
  29. package/dist/guardrails/plugins/base-plugin.js.map +1 -0
  30. package/dist/guardrails/plugins/index.d.ts +6 -0
  31. package/dist/guardrails/plugins/index.d.ts.map +1 -0
  32. package/dist/guardrails/plugins/index.js +6 -0
  33. package/dist/guardrails/plugins/index.js.map +1 -0
  34. package/dist/guardrails/plugins/pattern-blocker.d.ts +27 -0
  35. package/dist/guardrails/plugins/pattern-blocker.d.ts.map +1 -0
  36. package/dist/guardrails/plugins/pattern-blocker.js +140 -0
  37. package/dist/guardrails/plugins/pattern-blocker.js.map +1 -0
  38. package/dist/guardrails/plugins/pii-redactor/detectors.d.ts +40 -0
  39. package/dist/guardrails/plugins/pii-redactor/detectors.d.ts.map +1 -0
  40. package/dist/guardrails/plugins/pii-redactor/detectors.js +134 -0
  41. package/dist/guardrails/plugins/pii-redactor/detectors.js.map +1 -0
  42. package/dist/guardrails/plugins/pii-redactor/index.d.ts +28 -0
  43. package/dist/guardrails/plugins/pii-redactor/index.d.ts.map +1 -0
  44. package/dist/guardrails/plugins/pii-redactor/index.js +157 -0
  45. package/dist/guardrails/plugins/pii-redactor/index.js.map +1 -0
  46. package/dist/guardrails/plugins/pii-redactor/pseudonymizer.d.ts +33 -0
  47. package/dist/guardrails/plugins/pii-redactor/pseudonymizer.d.ts.map +1 -0
  48. package/dist/guardrails/plugins/pii-redactor/pseudonymizer.js +70 -0
  49. package/dist/guardrails/plugins/pii-redactor/pseudonymizer.js.map +1 -0
  50. package/dist/guardrails/plugins/rate-limiter.d.ts +28 -0
  51. package/dist/guardrails/plugins/rate-limiter.d.ts.map +1 -0
  52. package/dist/guardrails/plugins/rate-limiter.js +91 -0
  53. package/dist/guardrails/plugins/rate-limiter.js.map +1 -0
  54. package/dist/guardrails/plugins/token-limiter.d.ts +30 -0
  55. package/dist/guardrails/plugins/token-limiter.d.ts.map +1 -0
  56. package/dist/guardrails/plugins/token-limiter.js +98 -0
  57. package/dist/guardrails/plugins/token-limiter.js.map +1 -0
  58. package/dist/guardrails/service.d.ts +38 -0
  59. package/dist/guardrails/service.d.ts.map +1 -0
  60. package/dist/guardrails/service.js +183 -0
  61. package/dist/guardrails/service.js.map +1 -0
  62. package/dist/guardrails/types.d.ts +96 -0
  63. package/dist/guardrails/types.d.ts.map +1 -0
  64. package/dist/guardrails/types.js +2 -0
  65. package/dist/guardrails/types.js.map +1 -0
  66. package/dist/providers/duck-provider-enhanced.d.ts +2 -1
  67. package/dist/providers/duck-provider-enhanced.d.ts.map +1 -1
  68. package/dist/providers/duck-provider-enhanced.js +55 -6
  69. package/dist/providers/duck-provider-enhanced.js.map +1 -1
  70. package/dist/providers/enhanced-manager.d.ts +2 -1
  71. package/dist/providers/enhanced-manager.d.ts.map +1 -1
  72. package/dist/providers/enhanced-manager.js +3 -3
  73. package/dist/providers/enhanced-manager.js.map +1 -1
  74. package/dist/providers/manager.d.ts +3 -1
  75. package/dist/providers/manager.d.ts.map +1 -1
  76. package/dist/providers/manager.js +4 -2
  77. package/dist/providers/manager.js.map +1 -1
  78. package/dist/providers/provider.d.ts +3 -1
  79. package/dist/providers/provider.d.ts.map +1 -1
  80. package/dist/providers/provider.js +43 -3
  81. package/dist/providers/provider.js.map +1 -1
  82. package/dist/server.d.ts +1 -0
  83. package/dist/server.d.ts.map +1 -1
  84. package/dist/server.js +28 -6
  85. package/dist/server.js.map +1 -1
  86. package/dist/services/function-bridge.d.ts +3 -1
  87. package/dist/services/function-bridge.d.ts.map +1 -1
  88. package/dist/services/function-bridge.js +40 -1
  89. package/dist/services/function-bridge.js.map +1 -1
  90. package/package.json +5 -1
  91. package/src/config/config.ts +187 -1
  92. package/src/config/types.ts +73 -0
  93. package/src/guardrails/context.ts +37 -0
  94. package/src/guardrails/errors.ts +46 -0
  95. package/src/guardrails/index.ts +20 -0
  96. package/src/guardrails/plugins/base-plugin.ts +103 -0
  97. package/src/guardrails/plugins/index.ts +5 -0
  98. package/src/guardrails/plugins/pattern-blocker.ts +190 -0
  99. package/src/guardrails/plugins/pii-redactor/detectors.ts +200 -0
  100. package/src/guardrails/plugins/pii-redactor/index.ts +203 -0
  101. package/src/guardrails/plugins/pii-redactor/pseudonymizer.ts +91 -0
  102. package/src/guardrails/plugins/rate-limiter.ts +142 -0
  103. package/src/guardrails/plugins/token-limiter.ts +155 -0
  104. package/src/guardrails/service.ts +209 -0
  105. package/src/guardrails/types.ts +120 -0
  106. package/src/providers/duck-provider-enhanced.ts +76 -7
  107. package/src/providers/enhanced-manager.ts +5 -3
  108. package/src/providers/manager.ts +6 -3
  109. package/src/providers/provider.ts +57 -6
  110. package/src/server.ts +32 -6
  111. package/src/services/function-bridge.ts +53 -2
  112. package/tests/guardrails/config.test.ts +267 -0
  113. package/tests/guardrails/errors.test.ts +109 -0
  114. package/tests/guardrails/plugins/pattern-blocker.test.ts +309 -0
  115. package/tests/guardrails/plugins/pii-redactor.test.ts +1004 -0
  116. package/tests/guardrails/plugins/rate-limiter.test.ts +310 -0
  117. package/tests/guardrails/plugins/token-limiter.test.ts +216 -0
  118. package/tests/guardrails/service.test.ts +911 -0
  119. package/tests/mcp-bridge.test.ts +248 -0
  120. package/tests/providers.test.ts +739 -0
@@ -0,0 +1,40 @@
1
+ export type PIIType = 'email' | 'phone' | 'ssn' | 'api_key' | 'credit_card' | 'ip_address' | 'custom';
2
+ export interface PIIDetection {
3
+ type: PIIType;
4
+ value: string;
5
+ startIndex: number;
6
+ endIndex: number;
7
+ confidence: number;
8
+ }
9
+ export interface PIIDetectorConfig {
10
+ detectEmails: boolean;
11
+ detectPhones: boolean;
12
+ detectSSN: boolean;
13
+ detectAPIKeys: boolean;
14
+ detectCreditCards: boolean;
15
+ detectIPAddresses: boolean;
16
+ customPatterns: Array<{
17
+ name: string;
18
+ pattern: string;
19
+ placeholder: string;
20
+ }>;
21
+ allowlist: string[];
22
+ allowlistDomains: string[];
23
+ }
24
+ /**
25
+ * PII detector using regex patterns
26
+ */
27
+ export declare class PIIDetector {
28
+ private patterns;
29
+ private allowlist;
30
+ private allowlistDomains;
31
+ private customPatterns;
32
+ constructor(config: PIIDetectorConfig);
33
+ /**
34
+ * Detect PII in text
35
+ */
36
+ detect(text: string): PIIDetection[];
37
+ private isAllowlisted;
38
+ private calculateConfidence;
39
+ }
40
+ //# sourceMappingURL=detectors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectors.d.ts","sourceRoot":"","sources":["../../../../src/guardrails/plugins/pii-redactor/detectors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GACf,OAAO,GACP,OAAO,GACP,KAAK,GACL,SAAS,GACT,aAAa,GACb,YAAY,GACZ,QAAQ,CAAC;AAEb,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9E,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,cAAc,CAAmE;gBAE7E,MAAM,EAAE,iBAAiB;IAmErC;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE;IAkDpC,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,mBAAmB;CAuB5B"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * PII detector using regex patterns
3
+ */
4
+ export class PIIDetector {
5
+ patterns = new Map();
6
+ allowlist;
7
+ allowlistDomains;
8
+ customPatterns = [];
9
+ constructor(config) {
10
+ this.allowlist = new Set(config.allowlist.map((a) => a.toLowerCase()));
11
+ this.allowlistDomains = new Set(config.allowlistDomains.map((d) => d.toLowerCase()));
12
+ // Initialize built-in patterns
13
+ if (config.detectEmails) {
14
+ this.patterns.set('email', /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g);
15
+ }
16
+ if (config.detectPhones) {
17
+ // Handles multiple phone formats: US, international, with/without country code
18
+ this.patterns.set('phone', /\b(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}\b/g);
19
+ }
20
+ if (config.detectSSN) {
21
+ // US SSN format: XXX-XX-XXXX or XXXXXXXXX
22
+ this.patterns.set('ssn', /\b[0-9]{3}[-\s]?[0-9]{2}[-\s]?[0-9]{4}\b/g);
23
+ }
24
+ if (config.detectAPIKeys) {
25
+ // Common API key patterns
26
+ this.patterns.set('api_key', /\b(sk-[a-zA-Z0-9]{20,}|gsk_[a-zA-Z0-9]{20,}|api[_-]?key[_-]?[a-zA-Z0-9]{16,})\b/gi);
27
+ }
28
+ if (config.detectCreditCards) {
29
+ // Credit card patterns (Visa, Mastercard, Amex, Discover)
30
+ // Simplified - doesn't validate Luhn, just matches format
31
+ this.patterns.set('credit_card', /\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b/g);
32
+ }
33
+ if (config.detectIPAddresses) {
34
+ // IPv4 addresses
35
+ this.patterns.set('ip_address', /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g);
36
+ }
37
+ // Custom patterns
38
+ for (const custom of config.customPatterns) {
39
+ try {
40
+ this.customPatterns.push({
41
+ name: custom.name,
42
+ regex: new RegExp(custom.pattern, 'g'),
43
+ placeholder: custom.placeholder,
44
+ });
45
+ }
46
+ catch {
47
+ // Invalid regex, skip it
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Detect PII in text
53
+ */
54
+ detect(text) {
55
+ const detections = [];
56
+ // Check built-in patterns
57
+ for (const [type, pattern] of this.patterns) {
58
+ pattern.lastIndex = 0; // Reset regex state
59
+ let match;
60
+ while ((match = pattern.exec(text)) !== null) {
61
+ const value = match[0];
62
+ // Check allowlist
63
+ if (this.isAllowlisted(value, type)) {
64
+ continue;
65
+ }
66
+ detections.push({
67
+ type,
68
+ value,
69
+ startIndex: match.index,
70
+ endIndex: match.index + value.length,
71
+ confidence: this.calculateConfidence(type, value),
72
+ });
73
+ }
74
+ }
75
+ // Check custom patterns
76
+ for (const custom of this.customPatterns) {
77
+ custom.regex.lastIndex = 0;
78
+ let match;
79
+ while ((match = custom.regex.exec(text)) !== null) {
80
+ const value = match[0];
81
+ if (this.allowlist.has(value.toLowerCase())) {
82
+ continue;
83
+ }
84
+ detections.push({
85
+ type: 'custom',
86
+ value,
87
+ startIndex: match.index,
88
+ endIndex: match.index + value.length,
89
+ confidence: 0.9,
90
+ });
91
+ }
92
+ }
93
+ // Sort by position (for consistent pseudonymization)
94
+ return detections.sort((a, b) => a.startIndex - b.startIndex);
95
+ }
96
+ isAllowlisted(value, type) {
97
+ const lowerValue = value.toLowerCase();
98
+ if (this.allowlist.has(lowerValue)) {
99
+ return true;
100
+ }
101
+ // For emails, check domain allowlist
102
+ if (type === 'email') {
103
+ const domain = lowerValue.split('@')[1];
104
+ if (domain && this.allowlistDomains.has(domain)) {
105
+ return true;
106
+ }
107
+ }
108
+ return false;
109
+ }
110
+ calculateConfidence(type, value) {
111
+ // Simple confidence scoring based on format strictness
112
+ switch (type) {
113
+ case 'ssn':
114
+ return 0.95; // High confidence for strict format
115
+ case 'credit_card':
116
+ return 0.95; // High confidence for strict format
117
+ case 'email':
118
+ return 0.9;
119
+ case 'phone':
120
+ return 0.85;
121
+ case 'api_key':
122
+ // Higher confidence for longer keys or known prefixes
123
+ if (value.startsWith('sk-') || value.startsWith('gsk_')) {
124
+ return 0.95;
125
+ }
126
+ return 0.7; // Lower confidence due to possible false positives
127
+ case 'ip_address':
128
+ return 0.8;
129
+ default:
130
+ return 0.8;
131
+ }
132
+ }
133
+ }
134
+ //# sourceMappingURL=detectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectors.js","sourceRoot":"","sources":["../../../../src/guardrails/plugins/pii-redactor/detectors.ts"],"names":[],"mappings":"AA6BA;;GAEG;AACH,MAAM,OAAO,WAAW;IACd,QAAQ,GAAyB,IAAI,GAAG,EAAE,CAAC;IAC3C,SAAS,CAAc;IACvB,gBAAgB,CAAc;IAC9B,cAAc,GAAgE,EAAE,CAAC;IAEzF,YAAY,MAAyB;QACnC,IAAI,CAAC,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,gBAAgB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAErF,+BAA+B;QAC/B,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,IAAI,CAAC,QAAQ,CAAC,GAAG,CACf,OAAO,EACP,qDAAqD,CACtD,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,+EAA+E;YAC/E,IAAI,CAAC,QAAQ,CAAC,GAAG,CACf,OAAO,EACP,mEAAmE,CACpE,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,0CAA0C;YAC1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CACf,KAAK,EACL,2CAA2C,CAC5C,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACzB,0BAA0B;YAC1B,IAAI,CAAC,QAAQ,CAAC,GAAG,CACf,SAAS,EACT,mFAAmF,CACpF,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC7B,0DAA0D;YAC1D,0DAA0D;YAC1D,IAAI,CAAC,QAAQ,CAAC,GAAG,CACf,aAAa,EACb,6FAA6F,CAC9F,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC7B,iBAAiB;YACjB,IAAI,CAAC,QAAQ,CAAC,GAAG,CACf,YAAY,EACZ,gGAAgG,CACjG,CAAC;QACJ,CAAC;QAED,kBAAkB;QAClB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;YAC3C,IAAI,CAAC;gBACH,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC;oBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,KAAK,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC;oBACtC,WAAW,EAAE,MAAM,CAAC,WAAW;iBAChC,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,yBAAyB;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,IAAY;QACjB,MAAM,UAAU,GAAmB,EAAE,CAAC;QAEtC,0BAA0B;QAC1B,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC5C,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,oBAAoB;YAC3C,IAAI,KAAK,CAAC;YACV,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBAEvB,kBAAkB;gBAClB,IAAI,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;oBACpC,SAAS;gBACX,CAAC;gBAED,UAAU,CAAC,IAAI,CAAC;oBACd,IAAI;oBACJ,KAAK;oBACL,UAAU,EAAE,KAAK,CAAC,KAAK;oBACvB,QAAQ,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM;oBACpC,UAAU,EAAE,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC;iBAClD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,wBAAwB;QACxB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;YAC3B,IAAI,KAAK,CAAC;YACV,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBAClD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBAEvB,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;oBAC5C,SAAS;gBACX,CAAC;gBAED,UAAU,CAAC,IAAI,CAAC;oBACd,IAAI,EAAE,QAAQ;oBACd,KAAK;oBACL,UAAU,EAAE,KAAK,CAAC,KAAK;oBACvB,QAAQ,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM;oBACpC,UAAU,EAAE,GAAG;iBAChB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,qDAAqD;QACrD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;IAChE,CAAC;IAEO,aAAa,CAAC,KAAa,EAAE,IAAa;QAChD,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAEvC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,qCAAqC;QACrC,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACxC,IAAI,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChD,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,mBAAmB,CAAC,IAAa,EAAE,KAAa;QACtD,uDAAuD;QACvD,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,KAAK;gBACR,OAAO,IAAI,CAAC,CAAC,oCAAoC;YACnD,KAAK,aAAa;gBAChB,OAAO,IAAI,CAAC,CAAC,oCAAoC;YACnD,KAAK,OAAO;gBACV,OAAO,GAAG,CAAC;YACb,KAAK,OAAO;gBACV,OAAO,IAAI,CAAC;YACd,KAAK,SAAS;gBACZ,sDAAsD;gBACtD,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;oBACxD,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,OAAO,GAAG,CAAC,CAAC,mDAAmD;YACjE,KAAK,YAAY;gBACf,OAAO,GAAG,CAAC;YACb;gBACE,OAAO,GAAG,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,28 @@
1
+ import { BaseGuardrailPlugin } from '../base-plugin.js';
2
+ import { GuardrailPhase, GuardrailContext, GuardrailResult } from '../../types.js';
3
+ import { PIIDetector } from './detectors.js';
4
+ import { Pseudonymizer } from './pseudonymizer.js';
5
+ /**
6
+ * PII Redactor plugin - detects and redacts sensitive data
7
+ */
8
+ export declare class PIIRedactorPlugin extends BaseGuardrailPlugin {
9
+ name: string;
10
+ phases: GuardrailPhase[];
11
+ private detector;
12
+ private pseudonymizer;
13
+ private restoreOnResponse;
14
+ private logDetections;
15
+ initialize(config: Record<string, unknown>): Promise<void>;
16
+ execute(phase: GuardrailPhase, context: GuardrailContext): Promise<GuardrailResult>;
17
+ private redactPII;
18
+ private restorePII;
19
+ /**
20
+ * Get detector for testing
21
+ */
22
+ getDetector(): PIIDetector;
23
+ /**
24
+ * Get pseudonymizer for testing
25
+ */
26
+ getPseudonymizer(): Pseudonymizer;
27
+ }
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/guardrails/plugins/pii-redactor/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEnF,OAAO,EAAE,WAAW,EAAqB,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,mBAAmB;IACxD,IAAI,SAAkB;IACtB,MAAM,EAAE,cAAc,EAAE,CAA0E;IAElG,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,aAAa,CAAiB;IACtC,OAAO,CAAC,iBAAiB,CAAkB;IAC3C,OAAO,CAAC,aAAa,CAAiB;IAEhC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB1D,OAAO,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkBzF,OAAO,CAAC,SAAS;IA4EjB,OAAO,CAAC,UAAU;IAoDlB;;OAEG;IACH,WAAW,IAAI,WAAW;IAI1B;;OAEG;IACH,gBAAgB,IAAI,aAAa;CAGlC"}
@@ -0,0 +1,157 @@
1
+ import { BaseGuardrailPlugin } from '../base-plugin.js';
2
+ import { PIIDetector } from './detectors.js';
3
+ import { Pseudonymizer } from './pseudonymizer.js';
4
+ import { logger } from '../../../utils/logger.js';
5
+ /**
6
+ * PII Redactor plugin - detects and redacts sensitive data
7
+ */
8
+ export class PIIRedactorPlugin extends BaseGuardrailPlugin {
9
+ name = 'pii_redactor';
10
+ phases = ['pre_request', 'post_response', 'pre_tool_input', 'post_tool_output'];
11
+ detector;
12
+ pseudonymizer;
13
+ restoreOnResponse = false;
14
+ logDetections = true;
15
+ async initialize(config) {
16
+ await super.initialize(config);
17
+ const typedConfig = config;
18
+ const detectorConfig = {
19
+ detectEmails: typedConfig.detect_emails ?? true,
20
+ detectPhones: typedConfig.detect_phones ?? true,
21
+ detectSSN: typedConfig.detect_ssn ?? true,
22
+ detectAPIKeys: typedConfig.detect_api_keys ?? true,
23
+ detectCreditCards: typedConfig.detect_credit_cards ?? true,
24
+ detectIPAddresses: typedConfig.detect_ip_addresses ?? false,
25
+ customPatterns: typedConfig.custom_patterns ?? [],
26
+ allowlist: typedConfig.allowlist ?? [],
27
+ allowlistDomains: typedConfig.allowlist_domains ?? [],
28
+ };
29
+ this.detector = new PIIDetector(detectorConfig);
30
+ this.pseudonymizer = new Pseudonymizer();
31
+ this.restoreOnResponse = typedConfig.restore_on_response ?? false;
32
+ this.logDetections = typedConfig.log_detections ?? true;
33
+ this.priority = typedConfig.priority ?? 25;
34
+ }
35
+ async execute(phase, context) {
36
+ switch (phase) {
37
+ case 'pre_request':
38
+ case 'pre_tool_input':
39
+ return this.redactPII(context, phase);
40
+ case 'post_response':
41
+ case 'post_tool_output':
42
+ if (this.restoreOnResponse) {
43
+ return this.restorePII(context, phase);
44
+ }
45
+ return this.allow(context);
46
+ default:
47
+ return this.allow(context);
48
+ }
49
+ }
50
+ redactPII(context, phase) {
51
+ let textToScan;
52
+ let field;
53
+ if (phase === 'pre_request') {
54
+ textToScan = context.prompt || '';
55
+ field = 'prompt';
56
+ }
57
+ else {
58
+ textToScan = JSON.stringify(context.toolArgs || {});
59
+ field = 'toolArgs';
60
+ }
61
+ if (!textToScan) {
62
+ return Promise.resolve(this.allow(context));
63
+ }
64
+ const detections = this.detector.detect(textToScan);
65
+ if (detections.length === 0) {
66
+ return Promise.resolve(this.allow(context));
67
+ }
68
+ // Log detections
69
+ if (this.logDetections) {
70
+ logger.info(`PII detected in ${field}:`, {
71
+ requestId: context.requestId,
72
+ types: [...new Set(detections.map((d) => d.type))],
73
+ count: detections.length,
74
+ });
75
+ }
76
+ // Pseudonymize
77
+ const { text: redactedText, mappings } = this.pseudonymizer.pseudonymize(textToScan, detections);
78
+ // Store mappings for potential restoration
79
+ context.metadata.set('pii_mappings', mappings);
80
+ // Record modification
81
+ this.addModification(context, phase, field, `Redacted ${detections.length} PII items: ${[...new Set(detections.map((d) => d.type))].join(', ')}`, undefined, // Don't store original (contains PII)
82
+ undefined // Don't store new (would expose placeholder patterns)
83
+ );
84
+ // Apply modification
85
+ if (phase === 'pre_request') {
86
+ context.prompt = redactedText;
87
+ // Also update the last message if present
88
+ if (context.messages.length > 0) {
89
+ const lastIndex = context.messages.length - 1;
90
+ context.messages[lastIndex] = {
91
+ ...context.messages[lastIndex],
92
+ content: redactedText,
93
+ };
94
+ }
95
+ }
96
+ else {
97
+ try {
98
+ context.toolArgs = JSON.parse(redactedText);
99
+ }
100
+ catch {
101
+ // If parse fails, store as string
102
+ context.toolArgs = { _redacted: redactedText };
103
+ }
104
+ }
105
+ return Promise.resolve(this.modify(context));
106
+ }
107
+ restorePII(context, phase) {
108
+ const mappings = context.metadata.get('pii_mappings');
109
+ if (!mappings || mappings.size === 0) {
110
+ return Promise.resolve(this.allow(context));
111
+ }
112
+ let textToRestore;
113
+ if (phase === 'post_response') {
114
+ textToRestore = context.response || '';
115
+ }
116
+ else {
117
+ textToRestore =
118
+ typeof context.toolResult === 'string'
119
+ ? context.toolResult
120
+ : JSON.stringify(context.toolResult);
121
+ }
122
+ if (!textToRestore) {
123
+ return Promise.resolve(this.allow(context));
124
+ }
125
+ const restoredText = this.pseudonymizer.restore(textToRestore, mappings);
126
+ // Only modify if something changed
127
+ if (restoredText === textToRestore) {
128
+ return Promise.resolve(this.allow(context));
129
+ }
130
+ this.addModification(context, phase, phase === 'post_response' ? 'response' : 'toolResult', `Restored ${mappings.size} PII placeholders`);
131
+ if (phase === 'post_response') {
132
+ context.response = restoredText;
133
+ }
134
+ else {
135
+ try {
136
+ context.toolResult = JSON.parse(restoredText);
137
+ }
138
+ catch {
139
+ context.toolResult = restoredText;
140
+ }
141
+ }
142
+ return Promise.resolve(this.modify(context));
143
+ }
144
+ /**
145
+ * Get detector for testing
146
+ */
147
+ getDetector() {
148
+ return this.detector;
149
+ }
150
+ /**
151
+ * Get pseudonymizer for testing
152
+ */
153
+ getPseudonymizer() {
154
+ return this.pseudonymizer;
155
+ }
156
+ }
157
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/guardrails/plugins/pii-redactor/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { PIIDetection } from './detectors.js';
2
+ /**
3
+ * Pseudonymizer - replaces PII with numbered placeholders
4
+ * and supports optional restoration
5
+ */
6
+ export declare class Pseudonymizer {
7
+ private counters;
8
+ /**
9
+ * Pseudonymize text by replacing PII with placeholders
10
+ * Returns the modified text and a mapping for restoration
11
+ */
12
+ pseudonymize(text: string, detections: PIIDetection[]): {
13
+ text: string;
14
+ mappings: Map<string, string>;
15
+ };
16
+ /**
17
+ * Restore placeholders in text with original values
18
+ */
19
+ restore(text: string, mappings: Map<string, string>): string;
20
+ /**
21
+ * Generate a placeholder for a PII type
22
+ */
23
+ private generatePlaceholder;
24
+ /**
25
+ * Escape special regex characters in a string
26
+ */
27
+ private escapeRegex;
28
+ /**
29
+ * Reset counters (for testing)
30
+ */
31
+ reset(): void;
32
+ }
33
+ //# sourceMappingURL=pseudonymizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pseudonymizer.d.ts","sourceRoot":"","sources":["../../../../src/guardrails/plugins/pii-redactor/pseudonymizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAW,MAAM,gBAAgB,CAAC;AAEvD;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAmC;IAEnD;;;OAGG;IACH,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,YAAY,EAAE,GACzB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE;IAwBlD;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM;IAc5D;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Pseudonymizer - replaces PII with numbered placeholders
3
+ * and supports optional restoration
4
+ */
5
+ export class Pseudonymizer {
6
+ counters = new Map();
7
+ /**
8
+ * Pseudonymize text by replacing PII with placeholders
9
+ * Returns the modified text and a mapping for restoration
10
+ */
11
+ pseudonymize(text, detections) {
12
+ const mappings = new Map();
13
+ let result = text;
14
+ let offset = 0;
15
+ // Reset counters for consistent numbering
16
+ this.counters.clear();
17
+ for (const detection of detections) {
18
+ const placeholder = this.generatePlaceholder(detection.type);
19
+ mappings.set(placeholder, detection.value);
20
+ // Replace in text (accounting for previous replacements)
21
+ const start = detection.startIndex + offset;
22
+ const end = detection.endIndex + offset;
23
+ result = result.substring(0, start) + placeholder + result.substring(end);
24
+ // Adjust offset for next replacement
25
+ offset += placeholder.length - detection.value.length;
26
+ }
27
+ return { text: result, mappings };
28
+ }
29
+ /**
30
+ * Restore placeholders in text with original values
31
+ */
32
+ restore(text, mappings) {
33
+ let result = text;
34
+ for (const [placeholder, original] of mappings) {
35
+ // Use global replace to handle multiple occurrences
36
+ result = result.replace(new RegExp(this.escapeRegex(placeholder), 'g'), original);
37
+ }
38
+ return result;
39
+ }
40
+ /**
41
+ * Generate a placeholder for a PII type
42
+ */
43
+ generatePlaceholder(type) {
44
+ const count = (this.counters.get(type) || 0) + 1;
45
+ this.counters.set(type, count);
46
+ const typeLabels = {
47
+ email: 'EMAIL',
48
+ phone: 'PHONE',
49
+ ssn: 'SSN',
50
+ api_key: 'API_KEY',
51
+ credit_card: 'CARD',
52
+ ip_address: 'IP',
53
+ custom: 'REDACTED',
54
+ };
55
+ return `[${typeLabels[type]}_${count}]`;
56
+ }
57
+ /**
58
+ * Escape special regex characters in a string
59
+ */
60
+ escapeRegex(str) {
61
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
62
+ }
63
+ /**
64
+ * Reset counters (for testing)
65
+ */
66
+ reset() {
67
+ this.counters.clear();
68
+ }
69
+ }
70
+ //# sourceMappingURL=pseudonymizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pseudonymizer.js","sourceRoot":"","sources":["../../../../src/guardrails/plugins/pii-redactor/pseudonymizer.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,aAAa;IAChB,QAAQ,GAAyB,IAAI,GAAG,EAAE,CAAC;IAEnD;;;OAGG;IACH,YAAY,CACV,IAAY,EACZ,UAA0B;QAE1B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC3C,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,IAAI,MAAM,GAAG,CAAC,CAAC;QAEf,0CAA0C;QAC1C,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QAEtB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC7D,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;YAE3C,yDAAyD;YACzD,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,GAAG,MAAM,CAAC;YAC5C,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,GAAG,MAAM,CAAC;YACxC,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAE1E,qCAAqC;YACrC,MAAM,IAAI,WAAW,CAAC,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;QACxD,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,IAAY,EAAE,QAA6B;QACjD,IAAI,MAAM,GAAG,IAAI,CAAC;QAElB,KAAK,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,IAAI,QAAQ,EAAE,CAAC;YAC/C,oDAAoD;YACpD,MAAM,GAAG,MAAM,CAAC,OAAO,CACrB,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,EAC9C,QAAQ,CACT,CAAC;QACJ,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACK,mBAAmB,CAAC,IAAa;QACvC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACjD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAE/B,MAAM,UAAU,GAA4B;YAC1C,KAAK,EAAE,OAAO;YACd,KAAK,EAAE,OAAO;YACd,GAAG,EAAE,KAAK;YACV,OAAO,EAAE,SAAS;YAClB,WAAW,EAAE,MAAM;YACnB,UAAU,EAAE,IAAI;YAChB,MAAM,EAAE,UAAU;SACnB,CAAC;QAEF,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC;IAC1C,CAAC;IAED;;OAEG;IACK,WAAW,CAAC,GAAW;QAC7B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IACpD,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;CACF"}
@@ -0,0 +1,28 @@
1
+ import { BaseGuardrailPlugin } from './base-plugin.js';
2
+ import { GuardrailPhase, GuardrailContext, GuardrailResult } from '../types.js';
3
+ /**
4
+ * Rate limiter plugin - limits requests per minute/hour
5
+ */
6
+ export declare class RateLimiterPlugin extends BaseGuardrailPlugin {
7
+ name: string;
8
+ phases: GuardrailPhase[];
9
+ private requestsPerMinute;
10
+ private requestsPerHour;
11
+ private perProvider;
12
+ private burstAllowance;
13
+ private requestHistory;
14
+ initialize(config: Record<string, unknown>): Promise<void>;
15
+ execute(phase: GuardrailPhase, context: GuardrailContext): Promise<GuardrailResult>;
16
+ /**
17
+ * Get current request counts (for testing/monitoring)
18
+ */
19
+ getRequestCounts(key?: string): {
20
+ lastMinute: number;
21
+ lastHour: number;
22
+ };
23
+ /**
24
+ * Reset request history (for testing)
25
+ */
26
+ reset(): void;
27
+ }
28
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../../src/guardrails/plugins/rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAOhF;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,mBAAmB;IACxD,IAAI,SAAkB;IACtB,MAAM,EAAE,cAAc,EAAE,CAAmB;IAE3C,OAAO,CAAC,iBAAiB,CAAc;IACvC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,cAAc,CAAa;IAGnC,OAAO,CAAC,cAAc,CAA2C;IAE3D,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhE,OAAO,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAsFnF;;OAEG;IACH,gBAAgB,CAAC,GAAG,GAAE,MAAiB,GAAG;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAYlF;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,91 @@
1
+ import { BaseGuardrailPlugin } from './base-plugin.js';
2
+ /**
3
+ * Rate limiter plugin - limits requests per minute/hour
4
+ */
5
+ export class RateLimiterPlugin extends BaseGuardrailPlugin {
6
+ name = 'rate_limiter';
7
+ phases = ['pre_request'];
8
+ requestsPerMinute = 60;
9
+ requestsPerHour = 1000;
10
+ perProvider = false;
11
+ burstAllowance = 5;
12
+ // Request history: key is provider (or 'global'), value is array of timestamps
13
+ requestHistory = new Map();
14
+ async initialize(config) {
15
+ await super.initialize(config);
16
+ const typedConfig = config;
17
+ this.requestsPerMinute = typedConfig.requests_per_minute ?? 60;
18
+ this.requestsPerHour = typedConfig.requests_per_hour ?? 1000;
19
+ this.perProvider = typedConfig.per_provider ?? false;
20
+ this.burstAllowance = typedConfig.burst_allowance ?? 5;
21
+ this.priority = typedConfig.priority ?? 10;
22
+ }
23
+ execute(phase, context) {
24
+ if (phase !== 'pre_request') {
25
+ return Promise.resolve(this.allow(context));
26
+ }
27
+ const key = this.perProvider ? context.provider : 'global';
28
+ const now = Date.now();
29
+ // Get or create request history for this key
30
+ let history = this.requestHistory.get(key);
31
+ if (!history) {
32
+ history = [];
33
+ this.requestHistory.set(key, history);
34
+ }
35
+ // Clean up old entries (older than 1 hour)
36
+ const oneHourAgo = now - 60 * 60 * 1000;
37
+ history = history.filter((r) => r.timestamp > oneHourAgo);
38
+ // Remove empty keys to prevent unbounded Map growth with perProvider mode
39
+ if (history.length === 0) {
40
+ this.requestHistory.delete(key);
41
+ history = [];
42
+ }
43
+ else {
44
+ this.requestHistory.set(key, history);
45
+ }
46
+ // Count requests in last minute and last hour
47
+ const oneMinuteAgo = now - 60 * 1000;
48
+ const requestsLastMinute = history.filter((r) => r.timestamp > oneMinuteAgo).length;
49
+ const requestsLastHour = history.length;
50
+ // Check rate limits (with burst allowance)
51
+ const effectiveMinuteLimit = this.requestsPerMinute + this.burstAllowance;
52
+ const effectiveHourLimit = this.requestsPerHour + this.burstAllowance;
53
+ if (requestsLastMinute >= effectiveMinuteLimit) {
54
+ this.addViolation(context, phase, 'requests_per_minute', 'error', `Rate limit exceeded: ${requestsLastMinute} requests in the last minute (limit: ${this.requestsPerMinute})`, { requestsLastMinute, limit: this.requestsPerMinute });
55
+ return Promise.resolve(this.block(context, `Rate limit exceeded: ${requestsLastMinute}/${this.requestsPerMinute} requests per minute`));
56
+ }
57
+ if (requestsLastHour >= effectiveHourLimit) {
58
+ this.addViolation(context, phase, 'requests_per_hour', 'error', `Rate limit exceeded: ${requestsLastHour} requests in the last hour (limit: ${this.requestsPerHour})`, { requestsLastHour, limit: this.requestsPerHour });
59
+ return Promise.resolve(this.block(context, `Rate limit exceeded: ${requestsLastHour}/${this.requestsPerHour} requests per hour`));
60
+ }
61
+ // Log warning if approaching limit
62
+ if (requestsLastMinute >= this.requestsPerMinute * 0.8) {
63
+ this.addViolation(context, phase, 'requests_per_minute_warning', 'warning', `Approaching rate limit: ${requestsLastMinute}/${this.requestsPerMinute} requests per minute`, { requestsLastMinute, limit: this.requestsPerMinute });
64
+ }
65
+ // Record this request
66
+ history.push({ timestamp: now });
67
+ // Ensure history is stored in Map (needed after empty cleanup)
68
+ this.requestHistory.set(key, history);
69
+ return Promise.resolve(this.allow(context));
70
+ }
71
+ /**
72
+ * Get current request counts (for testing/monitoring)
73
+ */
74
+ getRequestCounts(key = 'global') {
75
+ const now = Date.now();
76
+ const history = this.requestHistory.get(key) || [];
77
+ const oneMinuteAgo = now - 60 * 1000;
78
+ const oneHourAgo = now - 60 * 60 * 1000;
79
+ return {
80
+ lastMinute: history.filter((r) => r.timestamp > oneMinuteAgo).length,
81
+ lastHour: history.filter((r) => r.timestamp > oneHourAgo).length,
82
+ };
83
+ }
84
+ /**
85
+ * Reset request history (for testing)
86
+ */
87
+ reset() {
88
+ this.requestHistory.clear();
89
+ }
90
+ }
91
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/guardrails/plugins/rate-limiter.ts"],"names":[],"mappings":"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"}