ai-shield-openai 0.2.0 → 0.3.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/package.json +2 -2
- package/src/wrapper.ts +75 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-shield-openai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "AI Shield wrapper for OpenAI SDK — automatic input/output scanning",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"openai": ">=4.0.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"ai-shield-core": "0.
|
|
23
|
+
"ai-shield-core": "0.3.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"openai": "^4.77.0",
|
package/src/wrapper.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AIShield,
|
|
3
|
+
ShieldConfig,
|
|
4
|
+
ScanContext,
|
|
5
|
+
ScanResult,
|
|
6
|
+
OutputScanConfig,
|
|
7
|
+
OutputScanResult,
|
|
8
|
+
} from "ai-shield-core";
|
|
2
9
|
|
|
3
10
|
// ============================================================
|
|
4
11
|
// OpenAI Shield Wrapper — Drop-in replacement
|
|
@@ -15,8 +22,19 @@ export interface ShieldedOpenAIConfig {
|
|
|
15
22
|
agentId?: string;
|
|
16
23
|
/** Custom scan context factory */
|
|
17
24
|
contextFactory?: (messages: ChatMessage[]) => ScanContext;
|
|
18
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Legacy: run the INPUT scan chain (heuristic + PII) over the output too.
|
|
27
|
+
* Default false. For real output-side defense (secret leak, SQL/XSS/shell
|
|
28
|
+
* injection, system-prompt leak) use `outputScan` below (OWASP LLM05).
|
|
29
|
+
*/
|
|
19
30
|
scanOutput?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Run the dedicated output scanner over the response (OWASP LLM05 / LLM02):
|
|
33
|
+
* secret leak, output injection, system-prompt leak, jailbreak, output-side
|
|
34
|
+
* PII. Pass `true` for defaults or an `OutputScanConfig`. Result lands in
|
|
35
|
+
* `response._shield.outputScan`.
|
|
36
|
+
*/
|
|
37
|
+
outputScan?: boolean | OutputScanConfig;
|
|
20
38
|
/** Callback when input is blocked */
|
|
21
39
|
onBlocked?: (result: ScanResult, messages: ChatMessage[]) => void;
|
|
22
40
|
/** Callback when input has warnings */
|
|
@@ -195,10 +213,29 @@ export class ShieldedOpenAI {
|
|
|
195
213
|
return { shieldInstance, context, userContent, inputResult, finalParams };
|
|
196
214
|
}
|
|
197
215
|
|
|
216
|
+
/** Run the dedicated OutputScanner if `outputScan` is configured. */
|
|
217
|
+
private async runOutputScan(
|
|
218
|
+
text: string,
|
|
219
|
+
context: ScanContext,
|
|
220
|
+
): Promise<OutputScanResult | undefined> {
|
|
221
|
+
if (!this.config.outputScan || !text) return undefined;
|
|
222
|
+
const cfg = this.config.outputScan === true ? {} : this.config.outputScan;
|
|
223
|
+
const mod = await import("ai-shield-core");
|
|
224
|
+
return mod.scanOutput(text, cfg, context);
|
|
225
|
+
}
|
|
226
|
+
|
|
198
227
|
/** Create chat completion with Shield protection (non-streaming) */
|
|
199
228
|
async createChatCompletion(
|
|
200
229
|
params: ChatCompletionParams,
|
|
201
|
-
): Promise<
|
|
230
|
+
): Promise<
|
|
231
|
+
ChatCompletion & {
|
|
232
|
+
_shield?: {
|
|
233
|
+
input: ScanResult;
|
|
234
|
+
output?: ScanResult;
|
|
235
|
+
outputScan?: OutputScanResult;
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
> {
|
|
202
239
|
const { shieldInstance, context, inputResult, finalParams } = await this.scanInput(params);
|
|
203
240
|
|
|
204
241
|
// --- Make the actual API call ---
|
|
@@ -215,17 +252,16 @@ export class ShieldedOpenAI {
|
|
|
215
252
|
}
|
|
216
253
|
|
|
217
254
|
// --- Scan output ---
|
|
255
|
+
const outputText = response.choices[0]?.message?.content ?? "";
|
|
218
256
|
let outputResult: ScanResult | undefined;
|
|
219
|
-
if (this.config.scanOutput) {
|
|
220
|
-
|
|
221
|
-
if (outputText) {
|
|
222
|
-
outputResult = await shieldInstance.scan(outputText, context);
|
|
223
|
-
}
|
|
257
|
+
if (this.config.scanOutput && outputText) {
|
|
258
|
+
outputResult = await shieldInstance.scan(outputText, context);
|
|
224
259
|
}
|
|
260
|
+
const outputScan = await this.runOutputScan(outputText, context);
|
|
225
261
|
|
|
226
262
|
return {
|
|
227
263
|
...response,
|
|
228
|
-
_shield: { input: inputResult, output: outputResult },
|
|
264
|
+
_shield: { input: inputResult, output: outputResult, outputScan },
|
|
229
265
|
};
|
|
230
266
|
}
|
|
231
267
|
|
|
@@ -250,6 +286,7 @@ export class ShieldedOpenAI {
|
|
|
250
286
|
this.config.scanOutput ?? false,
|
|
251
287
|
this.config.agentId,
|
|
252
288
|
finalParams.model,
|
|
289
|
+
this.config.outputScan,
|
|
253
290
|
);
|
|
254
291
|
}
|
|
255
292
|
|
|
@@ -305,12 +342,14 @@ export class ShieldedOpenAI {
|
|
|
305
342
|
export class ShieldedChatStream implements AsyncIterable<ChatCompletionChunk> {
|
|
306
343
|
private _inputResult: ScanResult;
|
|
307
344
|
private _outputResult: ScanResult | undefined;
|
|
345
|
+
private _outputScanResult: OutputScanResult | undefined;
|
|
308
346
|
private _done = false;
|
|
309
347
|
private _fullText = "";
|
|
310
348
|
private _stream: AsyncIterable<ChatCompletionChunk>;
|
|
311
349
|
private _shieldInstance: AIShield;
|
|
312
350
|
private _context: ScanContext;
|
|
313
351
|
private _scanOutput: boolean;
|
|
352
|
+
private _outputScan: boolean | OutputScanConfig | undefined;
|
|
314
353
|
private _agentId: string | undefined;
|
|
315
354
|
private _model: string;
|
|
316
355
|
private _usage: { prompt_tokens: number; completion_tokens: number } | undefined;
|
|
@@ -323,12 +362,14 @@ export class ShieldedChatStream implements AsyncIterable<ChatCompletionChunk> {
|
|
|
323
362
|
scanOutput: boolean,
|
|
324
363
|
agentId: string | undefined,
|
|
325
364
|
model: string,
|
|
365
|
+
outputScan?: boolean | OutputScanConfig,
|
|
326
366
|
) {
|
|
327
367
|
this._stream = stream;
|
|
328
368
|
this._inputResult = inputResult;
|
|
329
369
|
this._shieldInstance = shieldInstance;
|
|
330
370
|
this._context = context;
|
|
331
371
|
this._scanOutput = scanOutput;
|
|
372
|
+
this._outputScan = outputScan;
|
|
332
373
|
this._agentId = agentId;
|
|
333
374
|
this._model = model;
|
|
334
375
|
}
|
|
@@ -369,6 +410,15 @@ export class ShieldedChatStream implements AsyncIterable<ChatCompletionChunk> {
|
|
|
369
410
|
this._context,
|
|
370
411
|
);
|
|
371
412
|
}
|
|
413
|
+
if (this._outputScan && this._fullText) {
|
|
414
|
+
const cfg = this._outputScan === true ? {} : this._outputScan;
|
|
415
|
+
const mod = await import("ai-shield-core");
|
|
416
|
+
this._outputScanResult = await mod.scanOutput(
|
|
417
|
+
this._fullText,
|
|
418
|
+
cfg,
|
|
419
|
+
this._context,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
372
422
|
|
|
373
423
|
this._done = true;
|
|
374
424
|
}
|
|
@@ -378,14 +428,27 @@ export class ShieldedChatStream implements AsyncIterable<ChatCompletionChunk> {
|
|
|
378
428
|
return this._inputResult;
|
|
379
429
|
}
|
|
380
430
|
|
|
381
|
-
/** Output scan result (
|
|
431
|
+
/** Output scan result (legacy input-chain over output; after stream completes) */
|
|
382
432
|
get outputResult(): ScanResult | undefined {
|
|
383
433
|
return this._outputResult;
|
|
384
434
|
}
|
|
385
435
|
|
|
436
|
+
/** Dedicated OutputScanner result (after stream completes, if `outputScan` set) */
|
|
437
|
+
get outputScanResult(): OutputScanResult | undefined {
|
|
438
|
+
return this._outputScanResult;
|
|
439
|
+
}
|
|
440
|
+
|
|
386
441
|
/** Combined shield results */
|
|
387
|
-
get shieldResult(): {
|
|
388
|
-
|
|
442
|
+
get shieldResult(): {
|
|
443
|
+
input: ScanResult;
|
|
444
|
+
output?: ScanResult;
|
|
445
|
+
outputScan?: OutputScanResult;
|
|
446
|
+
} {
|
|
447
|
+
return {
|
|
448
|
+
input: this._inputResult,
|
|
449
|
+
output: this._outputResult,
|
|
450
|
+
outputScan: this._outputScanResult,
|
|
451
|
+
};
|
|
389
452
|
}
|
|
390
453
|
|
|
391
454
|
/** Whether the stream has completed */
|