agentshield-sdk 7.1.0 → 7.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,321 +1,321 @@
1
- 'use strict';
2
-
3
- /**
4
- * Circuit Breaker (#31), Shadow Mode (#33), and Rate Limiting (#5)
5
- *
6
- * - Circuit Breaker: Auto-shuts down an agent after too many threats in a time window.
7
- * - Shadow Mode: Detection-only mode — logs everything, blocks nothing.
8
- * - Rate Limiting: Tracks input patterns and flags anomalous spikes.
9
- */
10
-
11
- /**
12
- * Circuit breaker states.
13
- */
14
- const STATE = {
15
- CLOSED: 'closed', // Normal operation
16
- OPEN: 'open', // Tripped — all requests blocked
17
- HALF_OPEN: 'half_open' // Testing if safe to resume
18
- };
19
-
20
- class CircuitBreaker {
21
- /**
22
- * @param {object} [options]
23
- * @param {number} [options.threshold=5] - Number of threats to trip the breaker.
24
- * @param {number} [options.windowMs=60000] - Time window in ms (default: 1 minute).
25
- * @param {number} [options.cooldownMs=300000] - Cooldown before half-open (default: 5 minutes).
26
- * @param {Function} [options.onTrip] - Callback when breaker trips.
27
- * @param {Function} [options.onReset] - Callback when breaker resets.
28
- */
29
- constructor(options = {}) {
30
- this.threshold = options.threshold || 5;
31
- this.windowMs = options.windowMs || 60000;
32
- this.cooldownMs = options.cooldownMs || 300000;
33
- this.onTrip = options.onTrip || null;
34
- this.onReset = options.onReset || null;
35
-
36
- this.state = STATE.CLOSED;
37
- this.threatTimestamps = [];
38
- this.trippedAt = null;
39
- }
40
-
41
- /**
42
- * Records a threat event. Trips the breaker if threshold is exceeded.
43
- * @param {number} [count=1] - Number of threats to record.
44
- */
45
- recordThreat(count = 1) {
46
- const now = Date.now();
47
- for (let i = 0; i < count; i++) {
48
- this.threatTimestamps.push(now);
49
- }
50
-
51
- // Prune old timestamps outside the window
52
- const cutoff = now - this.windowMs;
53
- this.threatTimestamps = this.threatTimestamps.filter(t => t > cutoff);
54
-
55
- if (this.state === STATE.CLOSED && this.threatTimestamps.length >= this.threshold) {
56
- this._trip();
57
- }
58
- }
59
-
60
- /**
61
- * Checks if the breaker allows a request through.
62
- * @returns {object} { allowed: boolean, state: string, reason?: string }
63
- */
64
- check() {
65
- if (this.state === STATE.CLOSED) {
66
- return { allowed: true, state: this.state };
67
- }
68
-
69
- if (this.state === STATE.OPEN) {
70
- const elapsed = Date.now() - this.trippedAt;
71
- if (elapsed >= this.cooldownMs) {
72
- this.state = STATE.HALF_OPEN;
73
- return { allowed: true, state: this.state, reason: 'Testing after cooldown' };
74
- }
75
- const remainingMs = this.cooldownMs - elapsed;
76
- return {
77
- allowed: false,
78
- state: this.state,
79
- reason: `Circuit breaker tripped. Resumes in ${Math.ceil(remainingMs / 1000)}s.`
80
- };
81
- }
82
-
83
- // HALF_OPEN: allow one request to test
84
- return { allowed: true, state: this.state, reason: 'Half-open test request' };
85
- }
86
-
87
- /**
88
- * Reports the result of a half-open test request.
89
- * @param {boolean} safe - Whether the test request was safe.
90
- */
91
- reportTestResult(safe) {
92
- if (this.state !== STATE.HALF_OPEN) return;
93
-
94
- if (safe) {
95
- this._reset();
96
- } else {
97
- this._trip();
98
- }
99
- }
100
-
101
- /** @private */
102
- _trip() {
103
- this.state = STATE.OPEN;
104
- this.trippedAt = Date.now();
105
- if (this.onTrip) {
106
- try {
107
- this.onTrip({
108
- state: STATE.OPEN,
109
- threatCount: this.threatTimestamps.length,
110
- timestamp: this.trippedAt
111
- });
112
- } catch (err) {
113
- console.error('[Agent Shield] onTrip callback error:', err.message);
114
- }
115
- }
116
- }
117
-
118
- /** @private */
119
- _reset() {
120
- this.state = STATE.CLOSED;
121
- this.threatTimestamps = [];
122
- this.trippedAt = null;
123
- if (this.onReset) {
124
- try {
125
- this.onReset({ state: STATE.CLOSED, timestamp: Date.now() });
126
- } catch (err) {
127
- console.error('[Agent Shield] onReset callback error:', err.message);
128
- }
129
- }
130
- }
131
-
132
- /**
133
- * Manually reset the breaker.
134
- */
135
- reset() {
136
- this._reset();
137
- }
138
-
139
- /**
140
- * Returns current breaker status.
141
- * @returns {object}
142
- */
143
- getStatus() {
144
- return {
145
- state: this.state,
146
- recentThreats: this.threatTimestamps.length,
147
- threshold: this.threshold,
148
- trippedAt: this.trippedAt
149
- };
150
- }
151
- }
152
-
153
- // =========================================================================
154
- // SHADOW MODE
155
- // =========================================================================
156
-
157
- /**
158
- * Wraps an AgentShield instance in shadow mode.
159
- * Logs all detections but never blocks. Perfect for evaluation.
160
- *
161
- * @param {object} shield - An AgentShield instance.
162
- * @param {object} [options]
163
- * @param {Function} [options.logger] - Custom log function. Defaults to console.log.
164
- * @returns {object} - A shadow-mode wrapped shield with the same API.
165
- */
166
- const shadowMode = (shield, options = {}) => {
167
- const logger = options.logger || console.log;
168
- const log = [];
169
-
170
- const wrap = (methodName, original) => {
171
- return function (...args) {
172
- const result = original.apply(shield, args);
173
-
174
- // If it's a promise (async methods like from middleware), handle accordingly
175
- if (result && typeof result.then === 'function') {
176
- return result.then(res => {
177
- const entry = { method: methodName, result: res, timestamp: Date.now() };
178
- log.push(entry);
179
- if (log.length > 1000) log.shift();
180
- if (res.threats && res.threats.length > 0) {
181
- try { logger(`[Agent Shield Shadow] ${methodName}: ${res.threats.length} threat(s) detected (not blocked)`, res.threats.map(t => t.description)); } catch (e) { /* logger error */ }
182
- }
183
- // Never block in shadow mode
184
- if ('blocked' in res) res.blocked = false;
185
- return res;
186
- });
187
- }
188
-
189
- const entry = { method: methodName, result, timestamp: Date.now() };
190
- log.push(entry);
191
- if (log.length > 1000) log.shift();
192
-
193
- if (result.threats && result.threats.length > 0) {
194
- try { logger(`[Agent Shield Shadow] ${methodName}: ${result.threats.length} threat(s) detected (not blocked)`, result.threats.map(t => t.description)); } catch (e) { /* logger error */ }
195
- }
196
-
197
- // Never block in shadow mode
198
- if ('blocked' in result) result.blocked = false;
199
- return result;
200
- };
201
- };
202
-
203
- return {
204
- scan: wrap('scan', shield.scan),
205
- scanInput: wrap('scanInput', shield.scanInput),
206
- scanOutput: wrap('scanOutput', shield.scanOutput),
207
- scanToolCall: wrap('scanToolCall', shield.scanToolCall),
208
- scanBatch: wrap('scanBatch', shield.scanBatch),
209
- getStats: () => shield.getStats(),
210
- getLog: () => [...log],
211
- isShadowMode: true
212
- };
213
- };
214
-
215
- // =========================================================================
216
- // RATE LIMITER
217
- // =========================================================================
218
-
219
- class RateLimiter {
220
- /**
221
- * @param {object} [options]
222
- * @param {number} [options.maxRequests=100] - Max requests per window.
223
- * @param {number} [options.windowMs=60000] - Window size in ms.
224
- * @param {number} [options.maxThreatsPerWindow=10] - Max threats before flagging anomaly.
225
- * @param {Function} [options.onLimit] - Callback when rate limit hit.
226
- * @param {Function} [options.onAnomaly] - Callback when anomaly detected.
227
- */
228
- constructor(options = {}) {
229
- this.maxRequests = options.maxRequests || 100;
230
- this.windowMs = options.windowMs || 60000;
231
- this.maxThreatsPerWindow = options.maxThreatsPerWindow || 10;
232
- this.onLimit = options.onLimit || null;
233
- this.onAnomaly = options.onAnomaly || null;
234
-
235
- this.requestTimestamps = [];
236
- this.threatTimestamps = [];
237
- }
238
-
239
- /**
240
- * Records a request. Returns whether it's allowed.
241
- * @returns {object} { allowed: boolean, remaining: number, reason?: string }
242
- */
243
- recordRequest() {
244
- const now = Date.now();
245
- const cutoff = now - this.windowMs;
246
-
247
- this.requestTimestamps = this.requestTimestamps.filter(t => t > cutoff);
248
- this.requestTimestamps.push(now);
249
-
250
- if (this.requestTimestamps.length > this.maxRequests) {
251
- if (this.onLimit) {
252
- try {
253
- this.onLimit({ count: this.requestTimestamps.length, windowMs: this.windowMs });
254
- } catch (err) {
255
- console.error('[Agent Shield] onLimit callback error:', err.message);
256
- }
257
- }
258
- return {
259
- allowed: false,
260
- remaining: 0,
261
- reason: `Rate limit exceeded: ${this.requestTimestamps.length}/${this.maxRequests} requests in ${this.windowMs / 1000}s`
262
- };
263
- }
264
-
265
- return {
266
- allowed: true,
267
- remaining: this.maxRequests - this.requestTimestamps.length
268
- };
269
- }
270
-
271
- /**
272
- * Records threat detections. Flags anomalies if spike detected.
273
- * @param {number} [count=1] - Number of threats.
274
- * @returns {object} { anomaly: boolean, threatCount: number }
275
- */
276
- recordThreat(count = 1) {
277
- const now = Date.now();
278
- const cutoff = now - this.windowMs;
279
-
280
- for (let i = 0; i < count; i++) {
281
- this.threatTimestamps.push(now);
282
- }
283
- this.threatTimestamps = this.threatTimestamps.filter(t => t > cutoff);
284
-
285
- const isAnomaly = this.threatTimestamps.length >= this.maxThreatsPerWindow;
286
- if (isAnomaly && this.onAnomaly) {
287
- try {
288
- this.onAnomaly({ threatCount: this.threatTimestamps.length, windowMs: this.windowMs });
289
- } catch (err) {
290
- console.error('[Agent Shield] onAnomaly callback error:', err.message);
291
- }
292
- }
293
-
294
- return {
295
- anomaly: isAnomaly,
296
- threatCount: this.threatTimestamps.length
297
- };
298
- }
299
-
300
- /**
301
- * Returns current rate limiter status.
302
- * @returns {object}
303
- */
304
- getStatus() {
305
- const now = Date.now();
306
- const cutoff = now - this.windowMs;
307
- return {
308
- requests: this.requestTimestamps.filter(t => t > cutoff).length,
309
- maxRequests: this.maxRequests,
310
- threats: this.threatTimestamps.filter(t => t > cutoff).length,
311
- maxThreatsPerWindow: this.maxThreatsPerWindow
312
- };
313
- }
314
-
315
- reset() {
316
- this.requestTimestamps = [];
317
- this.threatTimestamps = [];
318
- }
319
- }
320
-
321
- module.exports = { CircuitBreaker, shadowMode, RateLimiter, STATE };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Circuit Breaker (#31), Shadow Mode (#33), and Rate Limiting (#5)
5
+ *
6
+ * - Circuit Breaker: Auto-shuts down an agent after too many threats in a time window.
7
+ * - Shadow Mode: Detection-only mode — logs everything, blocks nothing.
8
+ * - Rate Limiting: Tracks input patterns and flags anomalous spikes.
9
+ */
10
+
11
+ /**
12
+ * Circuit breaker states.
13
+ */
14
+ const STATE = {
15
+ CLOSED: 'closed', // Normal operation
16
+ OPEN: 'open', // Tripped — all requests blocked
17
+ HALF_OPEN: 'half_open' // Testing if safe to resume
18
+ };
19
+
20
+ class CircuitBreaker {
21
+ /**
22
+ * @param {object} [options]
23
+ * @param {number} [options.threshold=5] - Number of threats to trip the breaker.
24
+ * @param {number} [options.windowMs=60000] - Time window in ms (default: 1 minute).
25
+ * @param {number} [options.cooldownMs=300000] - Cooldown before half-open (default: 5 minutes).
26
+ * @param {Function} [options.onTrip] - Callback when breaker trips.
27
+ * @param {Function} [options.onReset] - Callback when breaker resets.
28
+ */
29
+ constructor(options = {}) {
30
+ this.threshold = (options.threshold != null && options.threshold > 0) ? options.threshold : 5;
31
+ this.windowMs = (options.windowMs != null && options.windowMs > 0) ? options.windowMs : 60000;
32
+ this.cooldownMs = (options.cooldownMs != null && options.cooldownMs > 0) ? options.cooldownMs : 300000;
33
+ this.onTrip = options.onTrip || null;
34
+ this.onReset = options.onReset || null;
35
+
36
+ this.state = STATE.CLOSED;
37
+ this.threatTimestamps = [];
38
+ this.trippedAt = null;
39
+ }
40
+
41
+ /**
42
+ * Records a threat event. Trips the breaker if threshold is exceeded.
43
+ * @param {number} [count=1] - Number of threats to record.
44
+ */
45
+ recordThreat(count = 1) {
46
+ const now = Date.now();
47
+ for (let i = 0; i < count; i++) {
48
+ this.threatTimestamps.push(now);
49
+ }
50
+
51
+ // Prune old timestamps outside the window
52
+ const cutoff = now - this.windowMs;
53
+ this.threatTimestamps = this.threatTimestamps.filter(t => t > cutoff);
54
+
55
+ if (this.state === STATE.CLOSED && this.threatTimestamps.length >= this.threshold) {
56
+ this._trip();
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Checks if the breaker allows a request through.
62
+ * @returns {object} { allowed: boolean, state: string, reason?: string }
63
+ */
64
+ check() {
65
+ if (this.state === STATE.CLOSED) {
66
+ return { allowed: true, state: this.state };
67
+ }
68
+
69
+ if (this.state === STATE.OPEN) {
70
+ const elapsed = Date.now() - this.trippedAt;
71
+ if (elapsed >= this.cooldownMs) {
72
+ this.state = STATE.HALF_OPEN;
73
+ return { allowed: true, state: this.state, reason: 'Testing after cooldown' };
74
+ }
75
+ const remainingMs = this.cooldownMs - elapsed;
76
+ return {
77
+ allowed: false,
78
+ state: this.state,
79
+ reason: `Circuit breaker tripped. Resumes in ${Math.ceil(remainingMs / 1000)}s.`
80
+ };
81
+ }
82
+
83
+ // HALF_OPEN: allow one request to test
84
+ return { allowed: true, state: this.state, reason: 'Half-open test request' };
85
+ }
86
+
87
+ /**
88
+ * Reports the result of a half-open test request.
89
+ * @param {boolean} safe - Whether the test request was safe.
90
+ */
91
+ reportTestResult(safe) {
92
+ if (this.state !== STATE.HALF_OPEN) return;
93
+
94
+ if (safe) {
95
+ this._reset();
96
+ } else {
97
+ this._trip();
98
+ }
99
+ }
100
+
101
+ /** @private */
102
+ _trip() {
103
+ this.state = STATE.OPEN;
104
+ this.trippedAt = Date.now();
105
+ if (this.onTrip) {
106
+ try {
107
+ this.onTrip({
108
+ state: STATE.OPEN,
109
+ threatCount: this.threatTimestamps.length,
110
+ timestamp: this.trippedAt
111
+ });
112
+ } catch (err) {
113
+ console.error('[Agent Shield] onTrip callback error:', err.message);
114
+ }
115
+ }
116
+ }
117
+
118
+ /** @private */
119
+ _reset() {
120
+ this.state = STATE.CLOSED;
121
+ this.threatTimestamps = [];
122
+ this.trippedAt = null;
123
+ if (this.onReset) {
124
+ try {
125
+ this.onReset({ state: STATE.CLOSED, timestamp: Date.now() });
126
+ } catch (err) {
127
+ console.error('[Agent Shield] onReset callback error:', err.message);
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Manually reset the breaker.
134
+ */
135
+ reset() {
136
+ this._reset();
137
+ }
138
+
139
+ /**
140
+ * Returns current breaker status.
141
+ * @returns {object}
142
+ */
143
+ getStatus() {
144
+ return {
145
+ state: this.state,
146
+ recentThreats: this.threatTimestamps.length,
147
+ threshold: this.threshold,
148
+ trippedAt: this.trippedAt
149
+ };
150
+ }
151
+ }
152
+
153
+ // =========================================================================
154
+ // SHADOW MODE
155
+ // =========================================================================
156
+
157
+ /**
158
+ * Wraps an AgentShield instance in shadow mode.
159
+ * Logs all detections but never blocks. Perfect for evaluation.
160
+ *
161
+ * @param {object} shield - An AgentShield instance.
162
+ * @param {object} [options]
163
+ * @param {Function} [options.logger] - Custom log function. Defaults to console.log.
164
+ * @returns {object} - A shadow-mode wrapped shield with the same API.
165
+ */
166
+ const shadowMode = (shield, options = {}) => {
167
+ const logger = options.logger || console.log;
168
+ const log = [];
169
+
170
+ const wrap = (methodName, original) => {
171
+ return function (...args) {
172
+ const result = original.apply(shield, args);
173
+
174
+ // If it's a promise (async methods like from middleware), handle accordingly
175
+ if (result && typeof result.then === 'function') {
176
+ return result.then(res => {
177
+ const entry = { method: methodName, result: res, timestamp: Date.now() };
178
+ log.push(entry);
179
+ if (log.length > 1000) log.shift();
180
+ if (res.threats && res.threats.length > 0) {
181
+ try { logger(`[Agent Shield Shadow] ${methodName}: ${res.threats.length} threat(s) detected (not blocked)`, res.threats.map(t => t.description)); } catch (e) { console.error('[Agent Shield] Shadow mode logger error:', e.message); }
182
+ }
183
+ // Never block in shadow mode
184
+ if ('blocked' in res) res.blocked = false;
185
+ return res;
186
+ });
187
+ }
188
+
189
+ const entry = { method: methodName, result, timestamp: Date.now() };
190
+ log.push(entry);
191
+ if (log.length > 1000) log.shift();
192
+
193
+ if (result.threats && result.threats.length > 0) {
194
+ try { logger(`[Agent Shield Shadow] ${methodName}: ${result.threats.length} threat(s) detected (not blocked)`, result.threats.map(t => t.description)); } catch (e) { console.error('[Agent Shield] Shadow mode logger error:', e.message); }
195
+ }
196
+
197
+ // Never block in shadow mode
198
+ if ('blocked' in result) result.blocked = false;
199
+ return result;
200
+ };
201
+ };
202
+
203
+ return {
204
+ scan: wrap('scan', shield.scan),
205
+ scanInput: wrap('scanInput', shield.scanInput),
206
+ scanOutput: wrap('scanOutput', shield.scanOutput),
207
+ scanToolCall: wrap('scanToolCall', shield.scanToolCall),
208
+ scanBatch: wrap('scanBatch', shield.scanBatch),
209
+ getStats: () => shield.getStats(),
210
+ getLog: () => [...log],
211
+ isShadowMode: true
212
+ };
213
+ };
214
+
215
+ // =========================================================================
216
+ // RATE LIMITER
217
+ // =========================================================================
218
+
219
+ class RateLimiter {
220
+ /**
221
+ * @param {object} [options]
222
+ * @param {number} [options.maxRequests=100] - Max requests per window.
223
+ * @param {number} [options.windowMs=60000] - Window size in ms.
224
+ * @param {number} [options.maxThreatsPerWindow=10] - Max threats before flagging anomaly.
225
+ * @param {Function} [options.onLimit] - Callback when rate limit hit.
226
+ * @param {Function} [options.onAnomaly] - Callback when anomaly detected.
227
+ */
228
+ constructor(options = {}) {
229
+ this.maxRequests = (options.maxRequests != null && options.maxRequests > 0) ? options.maxRequests : 100;
230
+ this.windowMs = (options.windowMs != null && options.windowMs > 0) ? options.windowMs : 60000;
231
+ this.maxThreatsPerWindow = (options.maxThreatsPerWindow != null && options.maxThreatsPerWindow > 0) ? options.maxThreatsPerWindow : 10;
232
+ this.onLimit = options.onLimit || null;
233
+ this.onAnomaly = options.onAnomaly || null;
234
+
235
+ this.requestTimestamps = [];
236
+ this.threatTimestamps = [];
237
+ }
238
+
239
+ /**
240
+ * Records a request. Returns whether it's allowed.
241
+ * @returns {object} { allowed: boolean, remaining: number, reason?: string }
242
+ */
243
+ recordRequest() {
244
+ const now = Date.now();
245
+ const cutoff = now - this.windowMs;
246
+
247
+ this.requestTimestamps = this.requestTimestamps.filter(t => t > cutoff);
248
+ this.requestTimestamps.push(now);
249
+
250
+ if (this.requestTimestamps.length > this.maxRequests) {
251
+ if (this.onLimit) {
252
+ try {
253
+ this.onLimit({ count: this.requestTimestamps.length, windowMs: this.windowMs });
254
+ } catch (err) {
255
+ console.error('[Agent Shield] onLimit callback error:', err.message);
256
+ }
257
+ }
258
+ return {
259
+ allowed: false,
260
+ remaining: 0,
261
+ reason: `Rate limit exceeded: ${this.requestTimestamps.length}/${this.maxRequests} requests in ${this.windowMs / 1000}s`
262
+ };
263
+ }
264
+
265
+ return {
266
+ allowed: true,
267
+ remaining: this.maxRequests - this.requestTimestamps.length
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Records threat detections. Flags anomalies if spike detected.
273
+ * @param {number} [count=1] - Number of threats.
274
+ * @returns {object} { anomaly: boolean, threatCount: number }
275
+ */
276
+ recordThreat(count = 1) {
277
+ const now = Date.now();
278
+ const cutoff = now - this.windowMs;
279
+
280
+ for (let i = 0; i < count; i++) {
281
+ this.threatTimestamps.push(now);
282
+ }
283
+ this.threatTimestamps = this.threatTimestamps.filter(t => t > cutoff);
284
+
285
+ const isAnomaly = this.threatTimestamps.length >= this.maxThreatsPerWindow;
286
+ if (isAnomaly && this.onAnomaly) {
287
+ try {
288
+ this.onAnomaly({ threatCount: this.threatTimestamps.length, windowMs: this.windowMs });
289
+ } catch (err) {
290
+ console.error('[Agent Shield] onAnomaly callback error:', err.message);
291
+ }
292
+ }
293
+
294
+ return {
295
+ anomaly: isAnomaly,
296
+ threatCount: this.threatTimestamps.length
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Returns current rate limiter status.
302
+ * @returns {object}
303
+ */
304
+ getStatus() {
305
+ const now = Date.now();
306
+ const cutoff = now - this.windowMs;
307
+ return {
308
+ requests: this.requestTimestamps.filter(t => t > cutoff).length,
309
+ maxRequests: this.maxRequests,
310
+ threats: this.threatTimestamps.filter(t => t > cutoff).length,
311
+ maxThreatsPerWindow: this.maxThreatsPerWindow
312
+ };
313
+ }
314
+
315
+ reset() {
316
+ this.requestTimestamps = [];
317
+ this.threatTimestamps = [];
318
+ }
319
+ }
320
+
321
+ module.exports = { CircuitBreaker, shadowMode, RateLimiter, STATE };
@@ -130,7 +130,7 @@ const INJECTION_PATTERNS = [
130
130
 
131
131
  // --- Role Hijacking ---
132
132
  {
133
- regex: /you\s+are\s+now\s+(?:a|an|the)\s+/i,
133
+ regex: /you\s+are\s+now\s+(?:(?:a|an|the)\s+)?(?:unrestricted|unfiltered|uncensored|evil|hacker|jailbroken|different|new\s+(?:ai|assistant|entity|agent|persona)|my\s+(?:personal|private|new)|free\s+(?:from|of)|without\s+(?:restrictions|limits|rules|filters))/i,
134
134
  severity: 'high',
135
135
  category: 'role_hijack',
136
136
  description: 'Text tries to change what an AI assistant thinks it is.',
@@ -840,7 +840,7 @@ const INJECTION_PATTERNS = [
840
840
  detail: 'System file access: attempts to read sensitive OS-level files.'
841
841
  },
842
842
  {
843
- regex: /list\s+(?:all\s+)?(?:available\s+)?(?:API\s*keys?|tokens?|passwords?|credentials?|secrets?)(?:\s*[,]\s*(?:API\s*keys?|tokens?|passwords?|credentials?|secrets?))*(?:\s+(?:you\s+)?(?:have\s+)?(?:access\s+to)?)?/i,
843
+ regex: /list\s+(?:all\s+)?(?:available\s+)?(?:API\s*keys?|tokens?|passwords?|credentials?|secrets?)(?:\s*,\s*\w[\w\s]*){0,5}(?:\s+(?:you\s+)?(?:have\s+)?(?:access\s+to)?)?/i,
844
844
  severity: 'critical',
845
845
  category: 'data_exfiltration',
846
846
  description: 'Text tries to enumerate all credentials and secrets the agent can access.',
@@ -1925,7 +1925,7 @@ const scanText = (text, options = {}) => {
1925
1925
  const maxSize = options.maxInputSize || MAX_INPUT_SIZE;
1926
1926
  const startTime = now();
1927
1927
 
1928
- if (typeof text !== 'string' || text.length < 10 || text.trim().length < 10) {
1928
+ if (typeof text !== 'string' || text.length === 0 || text.trim().length === 0) {
1929
1929
  return {
1930
1930
  status: 'safe',
1931
1931
  threats: [],