agentshield-sdk 7.0.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.
- package/CHANGELOG.md +191 -0
- package/LICENSE +21 -0
- package/README.md +975 -0
- package/bin/agent-shield.js +680 -0
- package/package.json +118 -0
- package/src/adaptive.js +330 -0
- package/src/agent-protocol.js +998 -0
- package/src/alert-tuning.js +480 -0
- package/src/allowlist.js +603 -0
- package/src/audit-immutable.js +914 -0
- package/src/audit-streaming.js +469 -0
- package/src/badges.js +196 -0
- package/src/behavior-profiling.js +289 -0
- package/src/benchmark-harness.js +804 -0
- package/src/canary.js +271 -0
- package/src/certification.js +563 -0
- package/src/circuit-breaker.js +321 -0
- package/src/compliance.js +617 -0
- package/src/confidence-tuning.js +324 -0
- package/src/confused-deputy.js +624 -0
- package/src/context-scoring.js +360 -0
- package/src/conversation.js +494 -0
- package/src/cost-optimizer.js +1024 -0
- package/src/ctf.js +462 -0
- package/src/detector-core.js +1999 -0
- package/src/distributed.js +359 -0
- package/src/document-scanner.js +795 -0
- package/src/embedding.js +307 -0
- package/src/encoding.js +429 -0
- package/src/enterprise.js +405 -0
- package/src/errors.js +100 -0
- package/src/eu-ai-act.js +523 -0
- package/src/fuzzer.js +764 -0
- package/src/honeypot.js +328 -0
- package/src/i18n-patterns.js +523 -0
- package/src/index.js +430 -0
- package/src/integrations.js +528 -0
- package/src/llm-redteam.js +670 -0
- package/src/main.js +741 -0
- package/src/main.mjs +38 -0
- package/src/mcp-bridge.js +542 -0
- package/src/mcp-certification.js +846 -0
- package/src/mcp-sdk-integration.js +355 -0
- package/src/mcp-security-runtime.js +741 -0
- package/src/mcp-server.js +740 -0
- package/src/middleware.js +208 -0
- package/src/model-finetuning.js +884 -0
- package/src/model-fingerprint.js +1042 -0
- package/src/multi-agent-trust.js +453 -0
- package/src/multi-agent.js +404 -0
- package/src/multimodal.js +296 -0
- package/src/nist-mapping.js +505 -0
- package/src/observability.js +330 -0
- package/src/openclaw.js +450 -0
- package/src/otel.js +544 -0
- package/src/owasp-2025.js +483 -0
- package/src/pii.js +390 -0
- package/src/plugin-marketplace.js +628 -0
- package/src/plugin-system.js +349 -0
- package/src/policy-dsl.js +775 -0
- package/src/policy-extended.js +635 -0
- package/src/policy.js +443 -0
- package/src/presets.js +409 -0
- package/src/production.js +557 -0
- package/src/prompt-leakage.js +321 -0
- package/src/rag-vulnerability.js +579 -0
- package/src/redteam.js +475 -0
- package/src/response-handler.js +429 -0
- package/src/scanners.js +357 -0
- package/src/self-healing.js +363 -0
- package/src/semantic.js +339 -0
- package/src/shield-score.js +250 -0
- package/src/sso-saml.js +897 -0
- package/src/stream-scanner.js +806 -0
- package/src/testing.js +505 -0
- package/src/threat-encyclopedia.js +629 -0
- package/src/threat-intel-network.js +1017 -0
- package/src/token-analysis.js +467 -0
- package/src/tool-guard.js +412 -0
- package/src/tool-output-validator.js +354 -0
- package/src/utils.js +83 -0
- package/src/watermark.js +235 -0
- package/src/worker-scanner.js +601 -0
- package/types/index.d.ts +2088 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield — Response Handler
|
|
5
|
+
*
|
|
6
|
+
* Configurable response handling when threats are detected. Supports multiple
|
|
7
|
+
* strategies: block, sanitize, redirect to human review, log-only, or custom.
|
|
8
|
+
*
|
|
9
|
+
* All detection runs locally — no data ever leaves your environment.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { scanText } = require('./detector-core');
|
|
13
|
+
|
|
14
|
+
// =========================================================================
|
|
15
|
+
// RESPONSE TEMPLATES
|
|
16
|
+
// =========================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Pre-built response templates for common threat handling scenarios.
|
|
20
|
+
*/
|
|
21
|
+
class ResponseTemplates {
|
|
22
|
+
/**
|
|
23
|
+
* Generate a safe block message when a threat is detected.
|
|
24
|
+
* @param {Array} threats - Array of threat objects from a scan result.
|
|
25
|
+
* @param {object} [options] - Template options.
|
|
26
|
+
* @param {string} [options.detailLevel='standard'] - Detail level: 'minimal', 'standard', 'verbose'.
|
|
27
|
+
* @returns {string} A safe block message.
|
|
28
|
+
*/
|
|
29
|
+
static block(threats, options = {}) {
|
|
30
|
+
const { detailLevel = 'standard' } = options;
|
|
31
|
+
const count = threats.length;
|
|
32
|
+
|
|
33
|
+
if (detailLevel === 'minimal') {
|
|
34
|
+
return 'This request was blocked for security reasons.';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (detailLevel === 'standard') {
|
|
38
|
+
const categories = [...new Set(threats.map(t => t.category))];
|
|
39
|
+
return `Request blocked: ${count} threat${count !== 1 ? 's' : ''} detected (${categories.join(', ')}). Contact your administrator if you believe this is an error.`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// verbose
|
|
43
|
+
const lines = [`Request blocked: ${count} threat${count !== 1 ? 's' : ''} detected.`];
|
|
44
|
+
for (const threat of threats) {
|
|
45
|
+
lines.push(` - [${threat.severity.toUpperCase()}] ${threat.category}: ${threat.description}`);
|
|
46
|
+
}
|
|
47
|
+
lines.push('Contact your administrator if you believe this is an error.');
|
|
48
|
+
return lines.join('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate a response for sanitized output.
|
|
53
|
+
* @param {string} original - The original text.
|
|
54
|
+
* @param {string} cleaned - The sanitized text.
|
|
55
|
+
* @returns {object} Response with sanitized text and note.
|
|
56
|
+
*/
|
|
57
|
+
static sanitized(original, cleaned) {
|
|
58
|
+
const modified = original !== cleaned;
|
|
59
|
+
return {
|
|
60
|
+
text: cleaned,
|
|
61
|
+
modified,
|
|
62
|
+
note: modified
|
|
63
|
+
? '[Agent Shield] Response was sanitized: some content was removed or redacted for security.'
|
|
64
|
+
: '[Agent Shield] Response passed sanitization without changes.'
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create a review ticket object for human review.
|
|
70
|
+
* @param {Array} threats - Array of threat objects.
|
|
71
|
+
* @param {object} [context] - Additional context.
|
|
72
|
+
* @returns {object} Review ticket object.
|
|
73
|
+
*/
|
|
74
|
+
static reviewTicket(threats, context = {}) {
|
|
75
|
+
return {
|
|
76
|
+
id: 'review_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8),
|
|
77
|
+
status: 'pending',
|
|
78
|
+
threats: threats.map(t => ({
|
|
79
|
+
severity: t.severity,
|
|
80
|
+
category: t.category,
|
|
81
|
+
description: t.description
|
|
82
|
+
})),
|
|
83
|
+
context: { ...context },
|
|
84
|
+
createdAt: Date.now(),
|
|
85
|
+
resolvedAt: null,
|
|
86
|
+
resolution: null
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// =========================================================================
|
|
92
|
+
// REVIEW QUEUE
|
|
93
|
+
// =========================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* In-memory queue for items redirected to human review.
|
|
97
|
+
*/
|
|
98
|
+
class ReviewQueue {
|
|
99
|
+
/**
|
|
100
|
+
* @param {object} [options]
|
|
101
|
+
* @param {number} [options.maxSize=1000] - Maximum number of items in the queue.
|
|
102
|
+
*/
|
|
103
|
+
constructor(options = {}) {
|
|
104
|
+
this.maxSize = options.maxSize || 1000;
|
|
105
|
+
|
|
106
|
+
/** @type {Map<string, object>} */
|
|
107
|
+
this._items = new Map();
|
|
108
|
+
this._totalAdded = 0;
|
|
109
|
+
this._totalApproved = 0;
|
|
110
|
+
this._totalRejected = 0;
|
|
111
|
+
|
|
112
|
+
console.log('[Agent Shield] ReviewQueue initialized (maxSize: %d)', this.maxSize);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Add an item to the review queue.
|
|
117
|
+
* @param {object} item - The review item (should include threats, context, etc.).
|
|
118
|
+
* @returns {string} The ticket ID.
|
|
119
|
+
*/
|
|
120
|
+
add(item) {
|
|
121
|
+
if (this._items.size >= this.maxSize) {
|
|
122
|
+
// Evict the oldest item
|
|
123
|
+
const oldestKey = this._items.keys().next().value;
|
|
124
|
+
this._items.delete(oldestKey);
|
|
125
|
+
console.log('[Agent Shield] ReviewQueue full, evicted oldest item: %s', oldestKey);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const ticket = ResponseTemplates.reviewTicket(
|
|
129
|
+
item.threats || [],
|
|
130
|
+
item.context || {}
|
|
131
|
+
);
|
|
132
|
+
ticket.originalContent = item.content || null;
|
|
133
|
+
|
|
134
|
+
this._items.set(ticket.id, ticket);
|
|
135
|
+
this._totalAdded++;
|
|
136
|
+
|
|
137
|
+
console.log('[Agent Shield] ReviewQueue item added: %s (%d threats)', ticket.id, ticket.threats.length);
|
|
138
|
+
return ticket.id;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get a review item by ticket ID.
|
|
143
|
+
* @param {string} ticketId - The ticket ID.
|
|
144
|
+
* @returns {object|null} The review item, or null if not found.
|
|
145
|
+
*/
|
|
146
|
+
get(ticketId) {
|
|
147
|
+
return this._items.get(ticketId) || null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Approve a review item.
|
|
152
|
+
* @param {string} ticketId - The ticket ID.
|
|
153
|
+
* @returns {boolean} True if the item was found and approved.
|
|
154
|
+
*/
|
|
155
|
+
approve(ticketId) {
|
|
156
|
+
const item = this._items.get(ticketId);
|
|
157
|
+
if (!item) return false;
|
|
158
|
+
|
|
159
|
+
item.status = 'approved';
|
|
160
|
+
item.resolvedAt = Date.now();
|
|
161
|
+
item.resolution = 'approved';
|
|
162
|
+
this._totalApproved++;
|
|
163
|
+
|
|
164
|
+
console.log('[Agent Shield] ReviewQueue item approved: %s', ticketId);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Reject a review item.
|
|
170
|
+
* @param {string} ticketId - The ticket ID.
|
|
171
|
+
* @returns {boolean} True if the item was found and rejected.
|
|
172
|
+
*/
|
|
173
|
+
reject(ticketId) {
|
|
174
|
+
const item = this._items.get(ticketId);
|
|
175
|
+
if (!item) return false;
|
|
176
|
+
|
|
177
|
+
item.status = 'rejected';
|
|
178
|
+
item.resolvedAt = Date.now();
|
|
179
|
+
item.resolution = 'rejected';
|
|
180
|
+
this._totalRejected++;
|
|
181
|
+
|
|
182
|
+
console.log('[Agent Shield] ReviewQueue item rejected: %s', ticketId);
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* List all pending review items.
|
|
188
|
+
* @returns {Array} Array of pending review items.
|
|
189
|
+
*/
|
|
190
|
+
getPending() {
|
|
191
|
+
const pending = [];
|
|
192
|
+
for (const item of this._items.values()) {
|
|
193
|
+
if (item.status === 'pending') {
|
|
194
|
+
pending.push(item);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return pending;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get queue statistics.
|
|
202
|
+
* @returns {object} Queue stats: { size, pending, approved, rejected, totalAdded, maxSize }.
|
|
203
|
+
*/
|
|
204
|
+
getStats() {
|
|
205
|
+
let pending = 0;
|
|
206
|
+
for (const item of this._items.values()) {
|
|
207
|
+
if (item.status === 'pending') pending++;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
size: this._items.size,
|
|
212
|
+
pending,
|
|
213
|
+
approved: this._totalApproved,
|
|
214
|
+
rejected: this._totalRejected,
|
|
215
|
+
totalAdded: this._totalAdded,
|
|
216
|
+
maxSize: this.maxSize
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// =========================================================================
|
|
222
|
+
// RESPONSE HANDLER
|
|
223
|
+
// =========================================================================
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Configurable response handler for detected threats.
|
|
227
|
+
* Routes threat detections through a chosen strategy: block, sanitize,
|
|
228
|
+
* redirect, log, or custom handler.
|
|
229
|
+
*/
|
|
230
|
+
class ResponseHandler {
|
|
231
|
+
/**
|
|
232
|
+
* @param {object} [options]
|
|
233
|
+
* @param {string} [options.strategy='block'] - Response strategy: 'block', 'sanitize', 'redirect', 'log', 'custom'.
|
|
234
|
+
* @param {Function} [options.customHandler] - Custom handler function (required if strategy is 'custom').
|
|
235
|
+
* @param {string} [options.blockMessage] - Custom block message (used with 'block' strategy).
|
|
236
|
+
* @param {string} [options.detailLevel='standard'] - Detail level for block messages: 'minimal', 'standard', 'verbose'.
|
|
237
|
+
* @param {ReviewQueue} [options.reviewQueue] - ReviewQueue instance for 'redirect' strategy.
|
|
238
|
+
*/
|
|
239
|
+
constructor(options = {}) {
|
|
240
|
+
this.strategy = options.strategy || 'block';
|
|
241
|
+
this.customHandler = options.customHandler || null;
|
|
242
|
+
this.blockMessage = options.blockMessage || null;
|
|
243
|
+
this.detailLevel = options.detailLevel || 'standard';
|
|
244
|
+
this.reviewQueue = options.reviewQueue || null;
|
|
245
|
+
|
|
246
|
+
if (this.strategy === 'custom' && typeof this.customHandler !== 'function') {
|
|
247
|
+
throw new Error('A "customHandler" function is required when strategy is "custom".');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (this.strategy === 'redirect' && !this.reviewQueue) {
|
|
251
|
+
this.reviewQueue = new ReviewQueue();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log('[Agent Shield] ResponseHandler initialized (strategy: %s)', this.strategy);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Handle a scan result using the configured strategy.
|
|
259
|
+
* @param {object} scanResult - The scan result from scanText or ToolOutputValidator.
|
|
260
|
+
* @param {object} [context] - Additional context (original text, tool name, etc.).
|
|
261
|
+
* @returns {object} Handler result: { action, response, original, threats }.
|
|
262
|
+
*/
|
|
263
|
+
handle(scanResult, context = {}) {
|
|
264
|
+
const threats = scanResult.threats || [];
|
|
265
|
+
const original = context.text || context.original || null;
|
|
266
|
+
|
|
267
|
+
// If no threats, pass through
|
|
268
|
+
if (threats.length === 0) {
|
|
269
|
+
return {
|
|
270
|
+
action: 'pass',
|
|
271
|
+
response: original,
|
|
272
|
+
original,
|
|
273
|
+
threats: []
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
switch (this.strategy) {
|
|
278
|
+
case 'block':
|
|
279
|
+
return this._handleBlock(threats, original, context);
|
|
280
|
+
case 'sanitize':
|
|
281
|
+
return this._handleSanitize(threats, original, context);
|
|
282
|
+
case 'redirect':
|
|
283
|
+
return this._handleRedirect(threats, original, context);
|
|
284
|
+
case 'log':
|
|
285
|
+
return this._handleLog(threats, original, context);
|
|
286
|
+
case 'custom':
|
|
287
|
+
return this._handleCustom(scanResult, original, context);
|
|
288
|
+
default:
|
|
289
|
+
console.log('[Agent Shield] Unknown strategy "%s", falling back to block', this.strategy);
|
|
290
|
+
return this._handleBlock(threats, original, context);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Block strategy: return a safe error message.
|
|
296
|
+
* @param {Array} threats
|
|
297
|
+
* @param {string} original
|
|
298
|
+
* @param {object} context
|
|
299
|
+
* @returns {object}
|
|
300
|
+
* @private
|
|
301
|
+
*/
|
|
302
|
+
_handleBlock(threats, original, context) {
|
|
303
|
+
const message = this.blockMessage || ResponseTemplates.block(threats, { detailLevel: this.detailLevel });
|
|
304
|
+
console.log('[Agent Shield] Response BLOCKED: %d threat(s) detected', threats.length);
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
action: 'block',
|
|
308
|
+
response: message,
|
|
309
|
+
original,
|
|
310
|
+
threats
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Sanitize strategy: remove threatening parts, return cleaned text.
|
|
316
|
+
* @param {Array} threats
|
|
317
|
+
* @param {string} original
|
|
318
|
+
* @param {object} context
|
|
319
|
+
* @returns {object}
|
|
320
|
+
* @private
|
|
321
|
+
*/
|
|
322
|
+
_handleSanitize(threats, original, context) {
|
|
323
|
+
if (!original || typeof original !== 'string') {
|
|
324
|
+
return {
|
|
325
|
+
action: 'sanitize',
|
|
326
|
+
response: '',
|
|
327
|
+
original,
|
|
328
|
+
threats
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Use scanText to locate threats, then sanitize
|
|
333
|
+
const { OutputSanitizer } = require('./tool-output-validator');
|
|
334
|
+
const cleaned = OutputSanitizer.sanitize(original, {
|
|
335
|
+
stripInvisible: true,
|
|
336
|
+
redactUrls: true,
|
|
337
|
+
redactCode: true
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const template = ResponseTemplates.sanitized(original, cleaned);
|
|
341
|
+
console.log('[Agent Shield] Response SANITIZED: %d threat(s), modified=%s', threats.length, template.modified);
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
action: 'sanitize',
|
|
345
|
+
response: template.text,
|
|
346
|
+
original,
|
|
347
|
+
threats,
|
|
348
|
+
note: template.note
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Redirect strategy: send to human review queue.
|
|
354
|
+
* @param {Array} threats
|
|
355
|
+
* @param {string} original
|
|
356
|
+
* @param {object} context
|
|
357
|
+
* @returns {object}
|
|
358
|
+
* @private
|
|
359
|
+
*/
|
|
360
|
+
_handleRedirect(threats, original, context) {
|
|
361
|
+
const ticketId = this.reviewQueue.add({
|
|
362
|
+
threats,
|
|
363
|
+
content: original,
|
|
364
|
+
context
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
console.log('[Agent Shield] Response REDIRECTED to review queue: %s', ticketId);
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
action: 'redirect',
|
|
371
|
+
response: `This response has been sent for human review. Ticket: ${ticketId}`,
|
|
372
|
+
original,
|
|
373
|
+
threats,
|
|
374
|
+
ticketId
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Log strategy: allow through but log the threat.
|
|
380
|
+
* @param {Array} threats
|
|
381
|
+
* @param {string} original
|
|
382
|
+
* @param {object} context
|
|
383
|
+
* @returns {object}
|
|
384
|
+
* @private
|
|
385
|
+
*/
|
|
386
|
+
_handleLog(threats, original, context) {
|
|
387
|
+
for (const threat of threats) {
|
|
388
|
+
console.log('[Agent Shield] Threat logged (pass-through): [%s] %s — %s', threat.severity, threat.category, threat.description);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
action: 'log',
|
|
393
|
+
response: original,
|
|
394
|
+
original,
|
|
395
|
+
threats
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Custom strategy: call user's handler function.
|
|
401
|
+
* @param {object} scanResult
|
|
402
|
+
* @param {string} original
|
|
403
|
+
* @param {object} context
|
|
404
|
+
* @returns {object}
|
|
405
|
+
* @private
|
|
406
|
+
*/
|
|
407
|
+
_handleCustom(scanResult, original, context) {
|
|
408
|
+
try {
|
|
409
|
+
const customResult = this.customHandler(scanResult, context);
|
|
410
|
+
console.log('[Agent Shield] Response handled by custom handler');
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
action: 'custom',
|
|
414
|
+
response: customResult,
|
|
415
|
+
original,
|
|
416
|
+
threats: scanResult.threats || []
|
|
417
|
+
};
|
|
418
|
+
} catch (err) {
|
|
419
|
+
console.log('[Agent Shield] Custom handler error: %s — falling back to block', err.message);
|
|
420
|
+
return this._handleBlock(scanResult.threats || [], original, context);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// =========================================================================
|
|
426
|
+
// EXPORTS
|
|
427
|
+
// =========================================================================
|
|
428
|
+
|
|
429
|
+
module.exports = { ResponseHandler, ResponseTemplates, ReviewQueue };
|