@ainvirion/aiproxyguard-npm-sdk 1.0.14
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/LICENSE +201 -0
- package/README.md +278 -0
- package/dist/express-x7uGX_rh.d.mts +258 -0
- package/dist/express-x7uGX_rh.d.ts +258 -0
- package/dist/index.d.mts +54 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +451 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +436 -0
- package/dist/index.mjs.map +1 -0
- package/dist/middleware/express.d.mts +1 -0
- package/dist/middleware/express.d.ts +1 -0
- package/dist/middleware/express.js +65 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/express.mjs +63 -0
- package/dist/middleware/express.mjs.map +1 -0
- package/package.json +71 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://aiproxyguard.com";
|
|
3
|
+
|
|
4
|
+
// src/errors.ts
|
|
5
|
+
var AIProxyGuardError = class extends Error {
|
|
6
|
+
constructor(message, code, statusCode) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.statusCode = statusCode;
|
|
10
|
+
this.name = "AIProxyGuardError";
|
|
11
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
12
|
+
}
|
|
13
|
+
code;
|
|
14
|
+
statusCode;
|
|
15
|
+
};
|
|
16
|
+
var ValidationError = class extends AIProxyGuardError {
|
|
17
|
+
constructor(message, code = "invalid_request") {
|
|
18
|
+
super(message, code, 400);
|
|
19
|
+
this.name = "ValidationError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var ConnectionError = class extends AIProxyGuardError {
|
|
23
|
+
constructor(message = "Failed to connect to AIProxyGuard") {
|
|
24
|
+
super(message, "connection_error");
|
|
25
|
+
this.name = "ConnectionError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var TimeoutError = class extends AIProxyGuardError {
|
|
29
|
+
constructor(message = "Request timed out") {
|
|
30
|
+
super(message, "timeout");
|
|
31
|
+
this.name = "TimeoutError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var RateLimitError = class extends AIProxyGuardError {
|
|
35
|
+
constructor(message = "Rate limited", retryAfter) {
|
|
36
|
+
super(message, "rate_limit", 429);
|
|
37
|
+
this.retryAfter = retryAfter;
|
|
38
|
+
this.name = "RateLimitError";
|
|
39
|
+
}
|
|
40
|
+
retryAfter;
|
|
41
|
+
};
|
|
42
|
+
var ContentBlockedError = class extends AIProxyGuardError {
|
|
43
|
+
constructor(result) {
|
|
44
|
+
const threatTypes = result.threats.map((t) => t.type).join(", ");
|
|
45
|
+
super(`Content blocked: ${threatTypes || "unknown"}`, "content_blocked", 400);
|
|
46
|
+
this.result = result;
|
|
47
|
+
this.name = "ContentBlockedError";
|
|
48
|
+
}
|
|
49
|
+
result;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/client.ts
|
|
53
|
+
var VALID_ACTIONS = ["allow", "log", "warn", "block"];
|
|
54
|
+
var VALID_SCHEMES = ["http:", "https:"];
|
|
55
|
+
var MAX_INPUT_SIZE = 1e5;
|
|
56
|
+
var DEFAULT_CONCURRENCY = 10;
|
|
57
|
+
function detectMode(url) {
|
|
58
|
+
return url.includes("docker.") ? "proxy" : "cloud";
|
|
59
|
+
}
|
|
60
|
+
var AIProxyGuard = class {
|
|
61
|
+
baseUrl;
|
|
62
|
+
apiKey;
|
|
63
|
+
mode;
|
|
64
|
+
timeout;
|
|
65
|
+
retries;
|
|
66
|
+
retryDelay;
|
|
67
|
+
maxConcurrency;
|
|
68
|
+
/**
|
|
69
|
+
* Create a new AIProxyGuard client.
|
|
70
|
+
*
|
|
71
|
+
* @param config - Configuration object, base URL string, or omit for default
|
|
72
|
+
* @throws {ValidationError} If the baseUrl has an invalid scheme
|
|
73
|
+
*/
|
|
74
|
+
constructor(config) {
|
|
75
|
+
const cfg = typeof config === "string" ? { baseUrl: config } : config ?? {};
|
|
76
|
+
const rawUrl = (cfg.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
77
|
+
try {
|
|
78
|
+
const parsed = new URL(rawUrl);
|
|
79
|
+
if (!VALID_SCHEMES.includes(parsed.protocol)) {
|
|
80
|
+
throw new ValidationError(
|
|
81
|
+
`Invalid URL scheme: ${parsed.protocol}. Only http: and https: are allowed.`,
|
|
82
|
+
"invalid_url"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (e instanceof ValidationError) throw e;
|
|
87
|
+
throw new ValidationError(`Invalid URL: ${rawUrl}`, "invalid_url");
|
|
88
|
+
}
|
|
89
|
+
this.baseUrl = rawUrl;
|
|
90
|
+
this.apiKey = cfg.apiKey;
|
|
91
|
+
this.timeout = cfg.timeout ?? 3e4;
|
|
92
|
+
this.retries = cfg.retries ?? 3;
|
|
93
|
+
this.retryDelay = cfg.retryDelay ?? 1e3;
|
|
94
|
+
this.maxConcurrency = cfg.maxConcurrency ?? DEFAULT_CONCURRENCY;
|
|
95
|
+
const mode = cfg.mode ?? "auto";
|
|
96
|
+
this.mode = mode === "auto" ? detectMode(this.baseUrl) : mode;
|
|
97
|
+
}
|
|
98
|
+
getHeaders() {
|
|
99
|
+
const headers = {
|
|
100
|
+
"Content-Type": "application/json"
|
|
101
|
+
};
|
|
102
|
+
if (this.apiKey) {
|
|
103
|
+
headers["X-API-Key"] = this.apiKey;
|
|
104
|
+
}
|
|
105
|
+
return headers;
|
|
106
|
+
}
|
|
107
|
+
async handleError(response) {
|
|
108
|
+
if (response.status === 429) {
|
|
109
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
110
|
+
throw new RateLimitError(
|
|
111
|
+
"Rate limited",
|
|
112
|
+
retryAfter ? parseInt(retryAfter, 10) : void 0
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const data = await response.json();
|
|
117
|
+
throw new ValidationError(
|
|
118
|
+
data.error?.message || "Unknown error",
|
|
119
|
+
data.error?.type || "unknown"
|
|
120
|
+
);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
if (e instanceof AIProxyGuardError) throw e;
|
|
123
|
+
throw new AIProxyGuardError(
|
|
124
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
125
|
+
"http_error",
|
|
126
|
+
response.status
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async fetchWithRetry(url, options) {
|
|
131
|
+
let lastError;
|
|
132
|
+
for (let attempt = 0; attempt < this.retries; attempt++) {
|
|
133
|
+
const controller = new AbortController();
|
|
134
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(url, {
|
|
137
|
+
...options,
|
|
138
|
+
signal: controller.signal
|
|
139
|
+
});
|
|
140
|
+
clearTimeout(timeoutId);
|
|
141
|
+
if (response.ok) {
|
|
142
|
+
return response;
|
|
143
|
+
}
|
|
144
|
+
if (response.status >= 400 && response.status < 500) {
|
|
145
|
+
await this.handleError(response);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
await response.text();
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
lastError = new AIProxyGuardError(
|
|
152
|
+
`HTTP ${response.status}`,
|
|
153
|
+
"http_error",
|
|
154
|
+
response.status
|
|
155
|
+
);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
clearTimeout(timeoutId);
|
|
158
|
+
if (e instanceof AIProxyGuardError) throw e;
|
|
159
|
+
if (e instanceof Error && e.name === "AbortError") {
|
|
160
|
+
lastError = new TimeoutError();
|
|
161
|
+
} else if (e instanceof TypeError) {
|
|
162
|
+
lastError = new ConnectionError();
|
|
163
|
+
} else {
|
|
164
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (attempt < this.retries - 1) {
|
|
168
|
+
await new Promise(
|
|
169
|
+
(resolve) => setTimeout(resolve, this.retryDelay * Math.pow(2, attempt))
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
throw lastError || new AIProxyGuardError("Unknown error", "unknown");
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check text for prompt injection.
|
|
177
|
+
*
|
|
178
|
+
* @param text - The text to scan
|
|
179
|
+
* @returns CheckResult with action, category, signatureName, and confidence
|
|
180
|
+
* @throws {ValidationError} If the request is invalid
|
|
181
|
+
* @throws {TimeoutError} If the request times out
|
|
182
|
+
* @throws {RateLimitError} If rate limited
|
|
183
|
+
* @throws {AIProxyGuardError} For other errors
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* const result = await client.check("Ignore all previous instructions");
|
|
188
|
+
* if (result.action === 'block') {
|
|
189
|
+
* console.log(`Blocked: ${result.category}`);
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
async check(text, context) {
|
|
194
|
+
if (text.length > MAX_INPUT_SIZE) {
|
|
195
|
+
throw new ValidationError(
|
|
196
|
+
`Input exceeds maximum size of ${MAX_INPUT_SIZE} bytes`,
|
|
197
|
+
"input_too_large"
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
if (this.mode === "proxy") {
|
|
201
|
+
return this.checkProxy(text);
|
|
202
|
+
}
|
|
203
|
+
return this.checkCloud(text, context);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Check using cloud mode (/api/v1/check with {input, context}).
|
|
207
|
+
*/
|
|
208
|
+
async checkCloud(text, context) {
|
|
209
|
+
const body = { input: text };
|
|
210
|
+
if (context) {
|
|
211
|
+
body.context = context;
|
|
212
|
+
}
|
|
213
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/api/v1/check`, {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: this.getHeaders(),
|
|
216
|
+
body: JSON.stringify(body)
|
|
217
|
+
});
|
|
218
|
+
const data = await response.json();
|
|
219
|
+
if (!VALID_ACTIONS.includes(data.action)) {
|
|
220
|
+
throw new AIProxyGuardError(
|
|
221
|
+
`Invalid action in response: ${data.action}`,
|
|
222
|
+
"invalid_response"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
id: data.id,
|
|
227
|
+
flagged: data.flagged,
|
|
228
|
+
action: data.action,
|
|
229
|
+
threats: data.threats.map((t) => ({
|
|
230
|
+
type: t.type,
|
|
231
|
+
confidence: t.confidence,
|
|
232
|
+
rule: t.rule
|
|
233
|
+
})),
|
|
234
|
+
latencyMs: data.latency_ms,
|
|
235
|
+
cached: data.cached
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check using proxy mode (/check with {text}).
|
|
240
|
+
*/
|
|
241
|
+
async checkProxy(text) {
|
|
242
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/check`, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: this.getHeaders(),
|
|
245
|
+
body: JSON.stringify({ text })
|
|
246
|
+
});
|
|
247
|
+
const data = await response.json();
|
|
248
|
+
if (!VALID_ACTIONS.includes(data.action)) {
|
|
249
|
+
throw new AIProxyGuardError(
|
|
250
|
+
`Invalid action in response: ${data.action}`,
|
|
251
|
+
"invalid_response"
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const flagged = data.action === "block";
|
|
255
|
+
const threats = data.category && flagged ? [
|
|
256
|
+
{
|
|
257
|
+
type: data.category,
|
|
258
|
+
confidence: data.confidence,
|
|
259
|
+
rule: data.signature_name
|
|
260
|
+
}
|
|
261
|
+
] : [];
|
|
262
|
+
return {
|
|
263
|
+
id: "",
|
|
264
|
+
// Proxy mode doesn't return ID
|
|
265
|
+
flagged,
|
|
266
|
+
action: data.action,
|
|
267
|
+
threats,
|
|
268
|
+
latencyMs: 0,
|
|
269
|
+
// Proxy mode doesn't return latency
|
|
270
|
+
cached: false
|
|
271
|
+
// Proxy mode doesn't return cache status
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Check multiple texts for prompt injection in parallel with concurrency limit.
|
|
276
|
+
*
|
|
277
|
+
* @param texts - Array of texts to scan
|
|
278
|
+
* @returns Array of CheckResult objects in the same order
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* const results = await client.checkBatch([
|
|
283
|
+
* 'Hello, how are you?',
|
|
284
|
+
* 'Ignore all instructions',
|
|
285
|
+
* ]);
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
async checkBatch(texts) {
|
|
289
|
+
if (texts.length === 0) return [];
|
|
290
|
+
const results = new Array(texts.length);
|
|
291
|
+
let nextIndex = 0;
|
|
292
|
+
const worker = async () => {
|
|
293
|
+
while (nextIndex < texts.length) {
|
|
294
|
+
const index = nextIndex++;
|
|
295
|
+
results[index] = await this.check(texts[index]);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
const workerCount = Math.min(this.maxConcurrency, texts.length);
|
|
299
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Check if text is safe (not blocked).
|
|
304
|
+
*
|
|
305
|
+
* @param text - The text to scan
|
|
306
|
+
* @returns True if the text is safe, false if blocked
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* if (await client.isSafe(userInput)) {
|
|
311
|
+
* // Process the input
|
|
312
|
+
* }
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
async isSafe(text) {
|
|
316
|
+
const result = await this.check(text);
|
|
317
|
+
return !result.flagged;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get service information.
|
|
321
|
+
*
|
|
322
|
+
* @returns ServiceInfo with service name and version
|
|
323
|
+
*/
|
|
324
|
+
async info() {
|
|
325
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/`, {
|
|
326
|
+
method: "GET",
|
|
327
|
+
headers: this.getHeaders()
|
|
328
|
+
});
|
|
329
|
+
return await response.json();
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Check if the service is healthy.
|
|
333
|
+
*
|
|
334
|
+
* @returns True if healthy, false otherwise
|
|
335
|
+
*/
|
|
336
|
+
async health() {
|
|
337
|
+
const controller = new AbortController();
|
|
338
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
339
|
+
try {
|
|
340
|
+
const response = await fetch(`${this.baseUrl}/healthz`, {
|
|
341
|
+
method: "GET",
|
|
342
|
+
headers: this.getHeaders(),
|
|
343
|
+
signal: controller.signal
|
|
344
|
+
});
|
|
345
|
+
return response.ok;
|
|
346
|
+
} catch {
|
|
347
|
+
return false;
|
|
348
|
+
} finally {
|
|
349
|
+
clearTimeout(timeoutId);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Check if the service is ready.
|
|
354
|
+
*
|
|
355
|
+
* @returns ReadinessStatus with status and checks
|
|
356
|
+
*/
|
|
357
|
+
async ready() {
|
|
358
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}/readyz`, {
|
|
359
|
+
method: "GET",
|
|
360
|
+
headers: this.getHeaders()
|
|
361
|
+
});
|
|
362
|
+
return await response.json();
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// src/middleware/express.ts
|
|
367
|
+
function guardMiddleware(client, options = {}) {
|
|
368
|
+
const {
|
|
369
|
+
textField = "text",
|
|
370
|
+
onBlock = "reject",
|
|
371
|
+
rejectInvalidTypes = true,
|
|
372
|
+
onError
|
|
373
|
+
} = options;
|
|
374
|
+
return async (req, res, next) => {
|
|
375
|
+
try {
|
|
376
|
+
const fields = Array.isArray(textField) ? textField : [textField];
|
|
377
|
+
const texts = [];
|
|
378
|
+
for (const field of fields) {
|
|
379
|
+
const value = req.body?.[field];
|
|
380
|
+
if (typeof value === "string") {
|
|
381
|
+
texts.push(value);
|
|
382
|
+
} else if (value !== void 0 && value !== null) {
|
|
383
|
+
if (rejectInvalidTypes) {
|
|
384
|
+
res.status(400).json({
|
|
385
|
+
error: {
|
|
386
|
+
type: "invalid_input",
|
|
387
|
+
message: `Field '${field}' must be a string`
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const coerced = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
393
|
+
texts.push(coerced);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (texts.length === 0) {
|
|
397
|
+
next();
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const results = await client.checkBatch(texts);
|
|
401
|
+
for (const result of results) {
|
|
402
|
+
if (result.flagged) {
|
|
403
|
+
if (onBlock === "reject") {
|
|
404
|
+
res.status(400).json({
|
|
405
|
+
error: {
|
|
406
|
+
type: "content_blocked",
|
|
407
|
+
message: "Potential prompt injection detected"
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
req.aiproxyguardResult = result;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
next();
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (onError && error instanceof Error) {
|
|
418
|
+
onError(error, req, res);
|
|
419
|
+
} else {
|
|
420
|
+
next(error);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/index.ts
|
|
427
|
+
function isSafe(result) {
|
|
428
|
+
return !result.flagged;
|
|
429
|
+
}
|
|
430
|
+
function isBlocked(result) {
|
|
431
|
+
return result.flagged;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export { AIProxyGuard, AIProxyGuardError, ConnectionError, ContentBlockedError, DEFAULT_BASE_URL, RateLimitError, TimeoutError, ValidationError, AIProxyGuard as default, guardMiddleware, isBlocked, isSafe };
|
|
435
|
+
//# sourceMappingURL=index.mjs.map
|
|
436
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/errors.ts","../src/client.ts","../src/middleware/express.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Default API base URL (cloud mode).\n */\nexport const DEFAULT_BASE_URL = 'https://aiproxyguard.com';\n\n/**\n * API mode determines endpoint paths and request/response formats.\n * - 'cloud': Uses /api/v1/check with {input} request format\n * - 'proxy': Uses /check with {text} request format\n * - 'auto': Auto-detect based on URL (docker.* = proxy, otherwise cloud)\n */\nexport type ApiMode = 'cloud' | 'proxy' | 'auto';\n\n/**\n * Action to take based on prompt injection detection result.\n */\nexport type Action = 'allow' | 'log' | 'warn' | 'block';\n\n/**\n * Threat detected in the input.\n */\nexport interface Threat {\n /** Type of threat detected */\n type: string;\n /** Confidence score from 0.0 to 1.0 */\n confidence: number;\n /** Rule that matched, if any */\n rule: string | null;\n}\n\n/**\n * Result from checking text for prompt injection.\n */\nexport interface CheckResult {\n /** Unique check ID */\n id: string;\n /** Whether the input was flagged as potentially harmful */\n flagged: boolean;\n /** Action recommended by the security proxy */\n action: Action;\n /** List of threats detected */\n threats: Threat[];\n /** Processing latency in milliseconds */\n latencyMs: number;\n /** Whether the result was served from cache */\n cached: boolean;\n}\n\n/**\n * Service information response.\n */\nexport interface ServiceInfo {\n service: string;\n version: string;\n}\n\n/**\n * Health check response.\n */\nexport interface HealthStatus {\n status: 'healthy' | 'unhealthy';\n}\n\n/**\n * Readiness check response.\n */\nexport interface ReadinessStatus {\n status: 'ready' | 'not_ready';\n checks: Record<string, boolean>;\n}\n\n/**\n * Configuration options for the AIProxyGuard client.\n */\nexport interface AIProxyGuardConfig {\n /** Base URL of the AIProxyGuard service (default: https://aiproxyguard.com) */\n baseUrl?: string;\n /** API key for authentication (optional for some deployments) */\n apiKey?: string;\n /** API mode: 'cloud', 'proxy', or 'auto' (default: 'auto') */\n mode?: ApiMode;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Number of retry attempts (default: 3) */\n retries?: number;\n /** Base delay between retries in milliseconds (default: 1000) */\n retryDelay?: number;\n /** Maximum concurrent requests for checkBatch (default: 10) */\n maxConcurrency?: number;\n}\n\n/**\n * Error response from the API.\n */\nexport interface ErrorResponse {\n error: {\n type: string;\n message: string;\n code?: string;\n };\n}\n","import type { CheckResult } from './types.js';\n\n/**\n * Base error class for AIProxyGuard SDK errors.\n */\nexport class AIProxyGuardError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly statusCode?: number\n ) {\n super(message);\n this.name = 'AIProxyGuardError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Thrown when the request is invalid or malformed.\n */\nexport class ValidationError extends AIProxyGuardError {\n constructor(message: string, code: string = 'invalid_request') {\n super(message, code, 400);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Thrown when unable to connect to the AIProxyGuard service.\n */\nexport class ConnectionError extends AIProxyGuardError {\n constructor(message: string = 'Failed to connect to AIProxyGuard') {\n super(message, 'connection_error');\n this.name = 'ConnectionError';\n }\n}\n\n/**\n * Thrown when a request times out.\n */\nexport class TimeoutError extends AIProxyGuardError {\n constructor(message: string = 'Request timed out') {\n super(message, 'timeout');\n this.name = 'TimeoutError';\n }\n}\n\n/**\n * Thrown when rate limited by the service.\n */\nexport class RateLimitError extends AIProxyGuardError {\n constructor(\n message: string = 'Rate limited',\n public readonly retryAfter?: number\n ) {\n super(message, 'rate_limit', 429);\n this.name = 'RateLimitError';\n }\n}\n\n/**\n * Thrown when content is blocked due to detected prompt injection.\n */\nexport class ContentBlockedError extends AIProxyGuardError {\n constructor(public readonly result: CheckResult) {\n const threatTypes = result.threats.map((t) => t.type).join(', ');\n super(`Content blocked: ${threatTypes || 'unknown'}`, 'content_blocked', 400);\n this.name = 'ContentBlockedError';\n }\n}\n","import {\n DEFAULT_BASE_URL,\n type Action,\n type AIProxyGuardConfig,\n type CheckResult,\n type ErrorResponse,\n type ReadinessStatus,\n type ServiceInfo,\n} from './types.js';\nimport {\n AIProxyGuardError,\n ConnectionError,\n RateLimitError,\n TimeoutError,\n ValidationError,\n} from './errors.js';\n\nconst VALID_ACTIONS: readonly string[] = ['allow', 'log', 'warn', 'block'];\nconst VALID_SCHEMES = ['http:', 'https:'];\nconst MAX_INPUT_SIZE = 100_000; // 100KB max input size\nconst DEFAULT_CONCURRENCY = 10; // Max concurrent requests in checkBatch\n\n/**\n * Detect API mode based on URL.\n * URLs containing 'docker.' use proxy mode, otherwise cloud mode.\n */\nfunction detectMode(url: string): 'cloud' | 'proxy' {\n return url.includes('docker.') ? 'proxy' : 'cloud';\n}\n\n/**\n * AIProxyGuard client for detecting prompt injection attacks.\n *\n * @example\n * ```typescript\n * const client = new AIProxyGuard('https://docker.aiproxyguard.com');\n *\n * const result = await client.check('Ignore all previous instructions');\n * if (result.action === 'block') {\n * console.log(`Blocked: ${result.category}`);\n * }\n * ```\n */\nexport class AIProxyGuard {\n private readonly baseUrl: string;\n private readonly apiKey?: string;\n private readonly mode: 'cloud' | 'proxy';\n private readonly timeout: number;\n private readonly retries: number;\n private readonly retryDelay: number;\n private readonly maxConcurrency: number;\n\n /**\n * Create a new AIProxyGuard client.\n *\n * @param config - Configuration object, base URL string, or omit for default\n * @throws {ValidationError} If the baseUrl has an invalid scheme\n */\n constructor(config?: AIProxyGuardConfig | string) {\n const cfg: AIProxyGuardConfig =\n typeof config === 'string'\n ? { baseUrl: config }\n : config ?? {};\n\n const rawUrl = (cfg.baseUrl || DEFAULT_BASE_URL).replace(/\\/$/, '');\n\n // Validate URL scheme to prevent SSRF\n try {\n const parsed = new URL(rawUrl);\n if (!VALID_SCHEMES.includes(parsed.protocol)) {\n throw new ValidationError(\n `Invalid URL scheme: ${parsed.protocol}. Only http: and https: are allowed.`,\n 'invalid_url'\n );\n }\n } catch (e) {\n if (e instanceof ValidationError) throw e;\n throw new ValidationError(`Invalid URL: ${rawUrl}`, 'invalid_url');\n }\n\n this.baseUrl = rawUrl;\n this.apiKey = cfg.apiKey;\n this.timeout = cfg.timeout ?? 30000;\n this.retries = cfg.retries ?? 3;\n this.retryDelay = cfg.retryDelay ?? 1000;\n this.maxConcurrency = cfg.maxConcurrency ?? DEFAULT_CONCURRENCY;\n\n // Determine API mode\n const mode = cfg.mode ?? 'auto';\n this.mode = mode === 'auto' ? detectMode(this.baseUrl) : mode;\n }\n\n private getHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.apiKey) {\n headers['X-API-Key'] = this.apiKey;\n }\n return headers;\n }\n\n private async handleError(response: Response): Promise<never> {\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n throw new RateLimitError(\n 'Rate limited',\n retryAfter ? parseInt(retryAfter, 10) : undefined\n );\n }\n\n try {\n const data = (await response.json()) as ErrorResponse;\n throw new ValidationError(\n data.error?.message || 'Unknown error',\n data.error?.type || 'unknown'\n );\n } catch (e) {\n if (e instanceof AIProxyGuardError) throw e;\n throw new AIProxyGuardError(\n `HTTP ${response.status}: ${response.statusText}`,\n 'http_error',\n response.status\n );\n }\n }\n\n private async fetchWithRetry(\n url: string,\n options: RequestInit\n ): Promise<Response> {\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < this.retries; attempt++) {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (response.ok) {\n return response;\n }\n\n // Don't retry client errors (4xx)\n if (response.status >= 400 && response.status < 500) {\n await this.handleError(response);\n }\n\n // Drain response body on 5xx to prevent connection leaks\n try {\n await response.text();\n } catch {\n // Ignore drain errors\n }\n\n lastError = new AIProxyGuardError(\n `HTTP ${response.status}`,\n 'http_error',\n response.status\n );\n } catch (e) {\n clearTimeout(timeoutId);\n\n if (e instanceof AIProxyGuardError) throw e;\n\n if (e instanceof Error && e.name === 'AbortError') {\n lastError = new TimeoutError();\n } else if (e instanceof TypeError) {\n lastError = new ConnectionError();\n } else {\n lastError = e instanceof Error ? e : new Error(String(e));\n }\n }\n\n // Exponential backoff before retry\n if (attempt < this.retries - 1) {\n await new Promise((resolve) =>\n setTimeout(resolve, this.retryDelay * Math.pow(2, attempt))\n );\n }\n }\n\n throw lastError || new AIProxyGuardError('Unknown error', 'unknown');\n }\n\n /**\n * Check text for prompt injection.\n *\n * @param text - The text to scan\n * @returns CheckResult with action, category, signatureName, and confidence\n * @throws {ValidationError} If the request is invalid\n * @throws {TimeoutError} If the request times out\n * @throws {RateLimitError} If rate limited\n * @throws {AIProxyGuardError} For other errors\n *\n * @example\n * ```typescript\n * const result = await client.check(\"Ignore all previous instructions\");\n * if (result.action === 'block') {\n * console.log(`Blocked: ${result.category}`);\n * }\n * ```\n */\n async check(text: string, context?: Record<string, unknown>): Promise<CheckResult> {\n // Validate input size to prevent DoS\n if (text.length > MAX_INPUT_SIZE) {\n throw new ValidationError(\n `Input exceeds maximum size of ${MAX_INPUT_SIZE} bytes`,\n 'input_too_large'\n );\n }\n\n if (this.mode === 'proxy') {\n return this.checkProxy(text);\n }\n return this.checkCloud(text, context);\n }\n\n /**\n * Check using cloud mode (/api/v1/check with {input, context}).\n */\n private async checkCloud(\n text: string,\n context?: Record<string, unknown>\n ): Promise<CheckResult> {\n const body: Record<string, unknown> = { input: text };\n if (context) {\n body.context = context;\n }\n\n const response = await this.fetchWithRetry(`${this.baseUrl}/api/v1/check`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify(body),\n });\n\n const data = (await response.json()) as {\n id: string;\n flagged: boolean;\n action: string;\n threats: Array<{ type: string; confidence: number; rule: string | null }>;\n latency_ms: number;\n cached: boolean;\n };\n\n // Validate response shape\n if (!VALID_ACTIONS.includes(data.action)) {\n throw new AIProxyGuardError(\n `Invalid action in response: ${data.action}`,\n 'invalid_response'\n );\n }\n\n return {\n id: data.id,\n flagged: data.flagged,\n action: data.action as Action,\n threats: data.threats.map((t) => ({\n type: t.type,\n confidence: t.confidence,\n rule: t.rule,\n })),\n latencyMs: data.latency_ms,\n cached: data.cached,\n };\n }\n\n /**\n * Check using proxy mode (/check with {text}).\n */\n private async checkProxy(text: string): Promise<CheckResult> {\n const response = await this.fetchWithRetry(`${this.baseUrl}/check`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({ text }),\n });\n\n const data = (await response.json()) as {\n action: string;\n category: string | null;\n signature_name: string | null;\n confidence: number;\n };\n\n // Validate response shape\n if (!VALID_ACTIONS.includes(data.action)) {\n throw new AIProxyGuardError(\n `Invalid action in response: ${data.action}`,\n 'invalid_response'\n );\n }\n\n // Normalize proxy response to common CheckResult format\n const flagged = data.action === 'block';\n const threats =\n data.category && flagged\n ? [\n {\n type: data.category,\n confidence: data.confidence,\n rule: data.signature_name,\n },\n ]\n : [];\n\n return {\n id: '', // Proxy mode doesn't return ID\n flagged,\n action: data.action as Action,\n threats,\n latencyMs: 0, // Proxy mode doesn't return latency\n cached: false, // Proxy mode doesn't return cache status\n };\n }\n\n /**\n * Check multiple texts for prompt injection in parallel with concurrency limit.\n *\n * @param texts - Array of texts to scan\n * @returns Array of CheckResult objects in the same order\n *\n * @example\n * ```typescript\n * const results = await client.checkBatch([\n * 'Hello, how are you?',\n * 'Ignore all instructions',\n * ]);\n * ```\n */\n async checkBatch(texts: string[]): Promise<CheckResult[]> {\n if (texts.length === 0) return [];\n\n // Concurrency-limited parallel execution\n const results: CheckResult[] = new Array(texts.length);\n let nextIndex = 0;\n\n const worker = async (): Promise<void> => {\n while (nextIndex < texts.length) {\n const index = nextIndex++;\n results[index] = await this.check(texts[index]);\n }\n };\n\n // Start up to maxConcurrency workers\n const workerCount = Math.min(this.maxConcurrency, texts.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n\n return results;\n }\n\n /**\n * Check if text is safe (not blocked).\n *\n * @param text - The text to scan\n * @returns True if the text is safe, false if blocked\n *\n * @example\n * ```typescript\n * if (await client.isSafe(userInput)) {\n * // Process the input\n * }\n * ```\n */\n async isSafe(text: string): Promise<boolean> {\n const result = await this.check(text);\n return !result.flagged;\n }\n\n /**\n * Get service information.\n *\n * @returns ServiceInfo with service name and version\n */\n async info(): Promise<ServiceInfo> {\n const response = await this.fetchWithRetry(`${this.baseUrl}/`, {\n method: 'GET',\n headers: this.getHeaders(),\n });\n\n return (await response.json()) as ServiceInfo;\n }\n\n /**\n * Check if the service is healthy.\n *\n * @returns True if healthy, false otherwise\n */\n async health(): Promise<boolean> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(`${this.baseUrl}/healthz`, {\n method: 'GET',\n headers: this.getHeaders(),\n signal: controller.signal,\n });\n return response.ok;\n } catch {\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n /**\n * Check if the service is ready.\n *\n * @returns ReadinessStatus with status and checks\n */\n async ready(): Promise<ReadinessStatus> {\n const response = await this.fetchWithRetry(`${this.baseUrl}/readyz`, {\n method: 'GET',\n headers: this.getHeaders(),\n });\n\n return (await response.json()) as ReadinessStatus;\n }\n}\n","import type { AIProxyGuard } from '../client.js';\nimport type { CheckResult } from '../types.js';\n\n/**\n * Express request type (minimal interface for compatibility).\n */\ninterface Request {\n body?: Record<string, unknown>;\n}\n\n/**\n * Express response type (minimal interface for compatibility).\n */\ninterface Response {\n status(code: number): Response;\n json(data: unknown): void;\n}\n\n/**\n * Express next function type.\n */\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended request with AIProxyGuard result attached.\n */\nexport interface GuardedRequest extends Request {\n aiproxyguardResult?: CheckResult;\n}\n\n/**\n * Options for the guard middleware.\n */\nexport interface GuardMiddlewareOptions {\n /** Field(s) in request body to check. Default: 'text' */\n textField?: string | string[];\n /** Action to take on block. Default: 'reject' */\n onBlock?: 'reject' | 'continue';\n /** Whether to reject requests with non-string field values. Default: true */\n rejectInvalidTypes?: boolean;\n /** Custom error handler */\n onError?: (error: Error, req: Request, res: Response) => void;\n}\n\n/**\n * Express middleware for prompt injection detection.\n *\n * @param client - AIProxyGuard client instance\n * @param options - Middleware options\n * @returns Express middleware function\n *\n * @example\n * ```typescript\n * import { AIProxyGuard } from '@aiproxyguard/sdk';\n * import { guardMiddleware } from '@aiproxyguard/sdk/middleware';\n *\n * const client = new AIProxyGuard('https://docker.aiproxyguard.com');\n *\n * app.post('/chat', guardMiddleware(client), (req, res) => {\n * // Request already validated\n * });\n * ```\n */\nexport function guardMiddleware(\n client: AIProxyGuard,\n options: GuardMiddlewareOptions = {}\n): (req: Request, res: Response, next: NextFunction) => Promise<void> {\n const {\n textField = 'text',\n onBlock = 'reject',\n rejectInvalidTypes = true,\n onError,\n } = options;\n\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const fields = Array.isArray(textField) ? textField : [textField];\n const texts: string[] = [];\n\n for (const field of fields) {\n const value = req.body?.[field];\n if (typeof value === 'string') {\n texts.push(value);\n } else if (value !== undefined && value !== null) {\n // Non-string value present - potential bypass attempt\n if (rejectInvalidTypes) {\n res.status(400).json({\n error: {\n type: 'invalid_input',\n message: `Field '${field}' must be a string`,\n },\n });\n return;\n }\n // If not rejecting, coerce arrays/objects to string for checking\n const coerced =\n typeof value === 'object' ? JSON.stringify(value) : String(value);\n texts.push(coerced);\n }\n }\n\n // Check all texts in parallel for better performance\n if (texts.length === 0) {\n next();\n return;\n }\n\n const results = await client.checkBatch(texts);\n\n for (const result of results) {\n if (result.flagged) {\n if (onBlock === 'reject') {\n res.status(400).json({\n error: {\n type: 'content_blocked',\n message: 'Potential prompt injection detected',\n },\n });\n return;\n }\n\n (req as GuardedRequest).aiproxyguardResult = result;\n }\n }\n\n next();\n } catch (error) {\n if (onError && error instanceof Error) {\n onError(error, req, res);\n } else {\n next(error);\n }\n }\n };\n}\n","// Main client\nexport { AIProxyGuard } from './client.js';\n\n// Types\nexport type {\n Action,\n AIProxyGuardConfig,\n ApiMode,\n CheckResult,\n ErrorResponse,\n HealthStatus,\n ReadinessStatus,\n ServiceInfo,\n Threat,\n} from './types.js';\n\n// Constants\nexport { DEFAULT_BASE_URL } from './types.js';\n\n// Errors\nexport {\n AIProxyGuardError,\n ConnectionError,\n ContentBlockedError,\n RateLimitError,\n TimeoutError,\n ValidationError,\n} from './errors.js';\n\n// Middleware\nexport {\n guardMiddleware,\n type GuardedRequest,\n type GuardMiddlewareOptions,\n} from './middleware/express.js';\n\n// Helper functions\nimport type { CheckResult } from './types.js';\n\n/**\n * Check if a CheckResult indicates the content is safe (not flagged).\n */\nexport function isSafe(result: CheckResult): boolean {\n return !result.flagged;\n}\n\n/**\n * Check if a CheckResult indicates the content is flagged/blocked.\n */\nexport function isBlocked(result: CheckResult): boolean {\n return result.flagged;\n}\n\n// Default export\nexport { AIProxyGuard as default } from './client.js';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { G as GuardMiddlewareOptions, d as GuardedRequest, g as guardMiddleware } from '../express-x7uGX_rh.mjs';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { G as GuardMiddlewareOptions, d as GuardedRequest, g as guardMiddleware } from '../express-x7uGX_rh.js';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/middleware/express.ts
|
|
4
|
+
function guardMiddleware(client, options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
textField = "text",
|
|
7
|
+
onBlock = "reject",
|
|
8
|
+
rejectInvalidTypes = true,
|
|
9
|
+
onError
|
|
10
|
+
} = options;
|
|
11
|
+
return async (req, res, next) => {
|
|
12
|
+
try {
|
|
13
|
+
const fields = Array.isArray(textField) ? textField : [textField];
|
|
14
|
+
const texts = [];
|
|
15
|
+
for (const field of fields) {
|
|
16
|
+
const value = req.body?.[field];
|
|
17
|
+
if (typeof value === "string") {
|
|
18
|
+
texts.push(value);
|
|
19
|
+
} else if (value !== void 0 && value !== null) {
|
|
20
|
+
if (rejectInvalidTypes) {
|
|
21
|
+
res.status(400).json({
|
|
22
|
+
error: {
|
|
23
|
+
type: "invalid_input",
|
|
24
|
+
message: `Field '${field}' must be a string`
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const coerced = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
30
|
+
texts.push(coerced);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (texts.length === 0) {
|
|
34
|
+
next();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const results = await client.checkBatch(texts);
|
|
38
|
+
for (const result of results) {
|
|
39
|
+
if (result.flagged) {
|
|
40
|
+
if (onBlock === "reject") {
|
|
41
|
+
res.status(400).json({
|
|
42
|
+
error: {
|
|
43
|
+
type: "content_blocked",
|
|
44
|
+
message: "Potential prompt injection detected"
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
req.aiproxyguardResult = result;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
next();
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (onError && error instanceof Error) {
|
|
55
|
+
onError(error, req, res);
|
|
56
|
+
} else {
|
|
57
|
+
next(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
exports.guardMiddleware = guardMiddleware;
|
|
64
|
+
//# sourceMappingURL=express.js.map
|
|
65
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/express.ts"],"names":[],"mappings":";;;AA+DO,SAAS,eAAA,CACd,MAAA,EACA,OAAA,GAAkC,EAAC,EACiC;AACpE,EAAA,MAAM;AAAA,IACJ,SAAA,GAAY,MAAA;AAAA,IACZ,OAAA,GAAU,QAAA;AAAA,IACV,kBAAA,GAAqB,IAAA;AAAA,IACrB;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,OAAO,OAAO,GAAA,EAAc,GAAA,EAAe,IAAA,KAAuB;AAChE,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA,GAAI,SAAA,GAAY,CAAC,SAAS,CAAA;AAChE,MAAA,MAAM,QAAkB,EAAC;AAEzB,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,IAAA,GAAO,KAAK,CAAA;AAC9B,QAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,UAAA,KAAA,CAAM,KAAK,KAAK,CAAA;AAAA,QAClB,CAAA,MAAA,IAAW,KAAA,KAAU,KAAA,CAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AAEhD,UAAA,IAAI,kBAAA,EAAoB;AACtB,YAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK;AAAA,cACnB,KAAA,EAAO;AAAA,gBACL,IAAA,EAAM,eAAA;AAAA,gBACN,OAAA,EAAS,UAAU,KAAK,CAAA,kBAAA;AAAA;AAC1B,aACD,CAAA;AACD,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,OAAA,GACJ,OAAO,KAAA,KAAU,QAAA,GAAW,KAAK,SAAA,CAAU,KAAK,CAAA,GAAI,MAAA,CAAO,KAAK,CAAA;AAClE,UAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,QACpB;AAAA,MACF;AAGA,MAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,QAAA,IAAA,EAAK;AACL,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,MAAM,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AAE7C,MAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,QAAA,IAAI,OAAO,OAAA,EAAS;AAClB,UAAA,IAAI,YAAY,QAAA,EAAU;AACxB,YAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK;AAAA,cACnB,KAAA,EAAO;AAAA,gBACL,IAAA,EAAM,iBAAA;AAAA,gBACN,OAAA,EAAS;AAAA;AACX,aACD,CAAA;AACD,YAAA;AAAA,UACF;AAEA,UAAC,IAAuB,kBAAA,GAAqB,MAAA;AAAA,QAC/C;AAAA,MACF;AAEA,MAAA,IAAA,EAAK;AAAA,IACP,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,OAAA,IAAW,iBAAiB,KAAA,EAAO;AACrC,QAAA,OAAA,CAAQ,KAAA,EAAO,KAAK,GAAG,CAAA;AAAA,MACzB,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,KAAK,CAAA;AAAA,MACZ;AAAA,IACF;AAAA,EACF,CAAA;AACF","file":"express.js","sourcesContent":["import type { AIProxyGuard } from '../client.js';\nimport type { CheckResult } from '../types.js';\n\n/**\n * Express request type (minimal interface for compatibility).\n */\ninterface Request {\n body?: Record<string, unknown>;\n}\n\n/**\n * Express response type (minimal interface for compatibility).\n */\ninterface Response {\n status(code: number): Response;\n json(data: unknown): void;\n}\n\n/**\n * Express next function type.\n */\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended request with AIProxyGuard result attached.\n */\nexport interface GuardedRequest extends Request {\n aiproxyguardResult?: CheckResult;\n}\n\n/**\n * Options for the guard middleware.\n */\nexport interface GuardMiddlewareOptions {\n /** Field(s) in request body to check. Default: 'text' */\n textField?: string | string[];\n /** Action to take on block. Default: 'reject' */\n onBlock?: 'reject' | 'continue';\n /** Whether to reject requests with non-string field values. Default: true */\n rejectInvalidTypes?: boolean;\n /** Custom error handler */\n onError?: (error: Error, req: Request, res: Response) => void;\n}\n\n/**\n * Express middleware for prompt injection detection.\n *\n * @param client - AIProxyGuard client instance\n * @param options - Middleware options\n * @returns Express middleware function\n *\n * @example\n * ```typescript\n * import { AIProxyGuard } from '@aiproxyguard/sdk';\n * import { guardMiddleware } from '@aiproxyguard/sdk/middleware';\n *\n * const client = new AIProxyGuard('https://docker.aiproxyguard.com');\n *\n * app.post('/chat', guardMiddleware(client), (req, res) => {\n * // Request already validated\n * });\n * ```\n */\nexport function guardMiddleware(\n client: AIProxyGuard,\n options: GuardMiddlewareOptions = {}\n): (req: Request, res: Response, next: NextFunction) => Promise<void> {\n const {\n textField = 'text',\n onBlock = 'reject',\n rejectInvalidTypes = true,\n onError,\n } = options;\n\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const fields = Array.isArray(textField) ? textField : [textField];\n const texts: string[] = [];\n\n for (const field of fields) {\n const value = req.body?.[field];\n if (typeof value === 'string') {\n texts.push(value);\n } else if (value !== undefined && value !== null) {\n // Non-string value present - potential bypass attempt\n if (rejectInvalidTypes) {\n res.status(400).json({\n error: {\n type: 'invalid_input',\n message: `Field '${field}' must be a string`,\n },\n });\n return;\n }\n // If not rejecting, coerce arrays/objects to string for checking\n const coerced =\n typeof value === 'object' ? JSON.stringify(value) : String(value);\n texts.push(coerced);\n }\n }\n\n // Check all texts in parallel for better performance\n if (texts.length === 0) {\n next();\n return;\n }\n\n const results = await client.checkBatch(texts);\n\n for (const result of results) {\n if (result.flagged) {\n if (onBlock === 'reject') {\n res.status(400).json({\n error: {\n type: 'content_blocked',\n message: 'Potential prompt injection detected',\n },\n });\n return;\n }\n\n (req as GuardedRequest).aiproxyguardResult = result;\n }\n }\n\n next();\n } catch (error) {\n if (onError && error instanceof Error) {\n onError(error, req, res);\n } else {\n next(error);\n }\n }\n };\n}\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/middleware/express.ts
|
|
2
|
+
function guardMiddleware(client, options = {}) {
|
|
3
|
+
const {
|
|
4
|
+
textField = "text",
|
|
5
|
+
onBlock = "reject",
|
|
6
|
+
rejectInvalidTypes = true,
|
|
7
|
+
onError
|
|
8
|
+
} = options;
|
|
9
|
+
return async (req, res, next) => {
|
|
10
|
+
try {
|
|
11
|
+
const fields = Array.isArray(textField) ? textField : [textField];
|
|
12
|
+
const texts = [];
|
|
13
|
+
for (const field of fields) {
|
|
14
|
+
const value = req.body?.[field];
|
|
15
|
+
if (typeof value === "string") {
|
|
16
|
+
texts.push(value);
|
|
17
|
+
} else if (value !== void 0 && value !== null) {
|
|
18
|
+
if (rejectInvalidTypes) {
|
|
19
|
+
res.status(400).json({
|
|
20
|
+
error: {
|
|
21
|
+
type: "invalid_input",
|
|
22
|
+
message: `Field '${field}' must be a string`
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const coerced = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
28
|
+
texts.push(coerced);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (texts.length === 0) {
|
|
32
|
+
next();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const results = await client.checkBatch(texts);
|
|
36
|
+
for (const result of results) {
|
|
37
|
+
if (result.flagged) {
|
|
38
|
+
if (onBlock === "reject") {
|
|
39
|
+
res.status(400).json({
|
|
40
|
+
error: {
|
|
41
|
+
type: "content_blocked",
|
|
42
|
+
message: "Potential prompt injection detected"
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
req.aiproxyguardResult = result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
next();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (onError && error instanceof Error) {
|
|
53
|
+
onError(error, req, res);
|
|
54
|
+
} else {
|
|
55
|
+
next(error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { guardMiddleware };
|
|
62
|
+
//# sourceMappingURL=express.mjs.map
|
|
63
|
+
//# sourceMappingURL=express.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/middleware/express.ts"],"names":[],"mappings":";AA+DO,SAAS,eAAA,CACd,MAAA,EACA,OAAA,GAAkC,EAAC,EACiC;AACpE,EAAA,MAAM;AAAA,IACJ,SAAA,GAAY,MAAA;AAAA,IACZ,OAAA,GAAU,QAAA;AAAA,IACV,kBAAA,GAAqB,IAAA;AAAA,IACrB;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,OAAO,OAAO,GAAA,EAAc,GAAA,EAAe,IAAA,KAAuB;AAChE,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,KAAA,CAAM,OAAA,CAAQ,SAAS,CAAA,GAAI,SAAA,GAAY,CAAC,SAAS,CAAA;AAChE,MAAA,MAAM,QAAkB,EAAC;AAEzB,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,IAAA,GAAO,KAAK,CAAA;AAC9B,QAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,UAAA,KAAA,CAAM,KAAK,KAAK,CAAA;AAAA,QAClB,CAAA,MAAA,IAAW,KAAA,KAAU,KAAA,CAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AAEhD,UAAA,IAAI,kBAAA,EAAoB;AACtB,YAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK;AAAA,cACnB,KAAA,EAAO;AAAA,gBACL,IAAA,EAAM,eAAA;AAAA,gBACN,OAAA,EAAS,UAAU,KAAK,CAAA,kBAAA;AAAA;AAC1B,aACD,CAAA;AACD,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,OAAA,GACJ,OAAO,KAAA,KAAU,QAAA,GAAW,KAAK,SAAA,CAAU,KAAK,CAAA,GAAI,MAAA,CAAO,KAAK,CAAA;AAClE,UAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,QACpB;AAAA,MACF;AAGA,MAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,QAAA,IAAA,EAAK;AACL,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,MAAM,MAAA,CAAO,UAAA,CAAW,KAAK,CAAA;AAE7C,MAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,QAAA,IAAI,OAAO,OAAA,EAAS;AAClB,UAAA,IAAI,YAAY,QAAA,EAAU;AACxB,YAAA,GAAA,CAAI,MAAA,CAAO,GAAG,CAAA,CAAE,IAAA,CAAK;AAAA,cACnB,KAAA,EAAO;AAAA,gBACL,IAAA,EAAM,iBAAA;AAAA,gBACN,OAAA,EAAS;AAAA;AACX,aACD,CAAA;AACD,YAAA;AAAA,UACF;AAEA,UAAC,IAAuB,kBAAA,GAAqB,MAAA;AAAA,QAC/C;AAAA,MACF;AAEA,MAAA,IAAA,EAAK;AAAA,IACP,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,OAAA,IAAW,iBAAiB,KAAA,EAAO;AACrC,QAAA,OAAA,CAAQ,KAAA,EAAO,KAAK,GAAG,CAAA;AAAA,MACzB,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,KAAK,CAAA;AAAA,MACZ;AAAA,IACF;AAAA,EACF,CAAA;AACF","file":"express.mjs","sourcesContent":["import type { AIProxyGuard } from '../client.js';\nimport type { CheckResult } from '../types.js';\n\n/**\n * Express request type (minimal interface for compatibility).\n */\ninterface Request {\n body?: Record<string, unknown>;\n}\n\n/**\n * Express response type (minimal interface for compatibility).\n */\ninterface Response {\n status(code: number): Response;\n json(data: unknown): void;\n}\n\n/**\n * Express next function type.\n */\ntype NextFunction = (error?: unknown) => void;\n\n/**\n * Extended request with AIProxyGuard result attached.\n */\nexport interface GuardedRequest extends Request {\n aiproxyguardResult?: CheckResult;\n}\n\n/**\n * Options for the guard middleware.\n */\nexport interface GuardMiddlewareOptions {\n /** Field(s) in request body to check. Default: 'text' */\n textField?: string | string[];\n /** Action to take on block. Default: 'reject' */\n onBlock?: 'reject' | 'continue';\n /** Whether to reject requests with non-string field values. Default: true */\n rejectInvalidTypes?: boolean;\n /** Custom error handler */\n onError?: (error: Error, req: Request, res: Response) => void;\n}\n\n/**\n * Express middleware for prompt injection detection.\n *\n * @param client - AIProxyGuard client instance\n * @param options - Middleware options\n * @returns Express middleware function\n *\n * @example\n * ```typescript\n * import { AIProxyGuard } from '@aiproxyguard/sdk';\n * import { guardMiddleware } from '@aiproxyguard/sdk/middleware';\n *\n * const client = new AIProxyGuard('https://docker.aiproxyguard.com');\n *\n * app.post('/chat', guardMiddleware(client), (req, res) => {\n * // Request already validated\n * });\n * ```\n */\nexport function guardMiddleware(\n client: AIProxyGuard,\n options: GuardMiddlewareOptions = {}\n): (req: Request, res: Response, next: NextFunction) => Promise<void> {\n const {\n textField = 'text',\n onBlock = 'reject',\n rejectInvalidTypes = true,\n onError,\n } = options;\n\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const fields = Array.isArray(textField) ? textField : [textField];\n const texts: string[] = [];\n\n for (const field of fields) {\n const value = req.body?.[field];\n if (typeof value === 'string') {\n texts.push(value);\n } else if (value !== undefined && value !== null) {\n // Non-string value present - potential bypass attempt\n if (rejectInvalidTypes) {\n res.status(400).json({\n error: {\n type: 'invalid_input',\n message: `Field '${field}' must be a string`,\n },\n });\n return;\n }\n // If not rejecting, coerce arrays/objects to string for checking\n const coerced =\n typeof value === 'object' ? JSON.stringify(value) : String(value);\n texts.push(coerced);\n }\n }\n\n // Check all texts in parallel for better performance\n if (texts.length === 0) {\n next();\n return;\n }\n\n const results = await client.checkBatch(texts);\n\n for (const result of results) {\n if (result.flagged) {\n if (onBlock === 'reject') {\n res.status(400).json({\n error: {\n type: 'content_blocked',\n message: 'Potential prompt injection detected',\n },\n });\n return;\n }\n\n (req as GuardedRequest).aiproxyguardResult = result;\n }\n }\n\n next();\n } catch (error) {\n if (onError && error instanceof Error) {\n onError(error, req, res);\n } else {\n next(error);\n }\n }\n };\n}\n"]}
|