ai-sdk-guardrails 1.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/LICENSE +21 -0
- package/README.md +729 -0
- package/dist/chunk-BNGJDZMX.js +218 -0
- package/dist/guardrails/input.cjs +487 -0
- package/dist/guardrails/input.d.cts +36 -0
- package/dist/guardrails/input.d.ts +36 -0
- package/dist/guardrails/input.js +445 -0
- package/dist/guardrails/output.cjs +674 -0
- package/dist/guardrails/output.d.cts +46 -0
- package/dist/guardrails/output.d.ts +46 -0
- package/dist/guardrails/output.js +628 -0
- package/dist/index-CWIb12lh.d.cts +121 -0
- package/dist/index-CWIb12lh.d.ts +121 -0
- package/dist/index.cjs +245 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -0
- package/package.json +91 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/core.ts
|
|
2
|
+
import {
|
|
3
|
+
generateText,
|
|
4
|
+
generateObject,
|
|
5
|
+
streamText,
|
|
6
|
+
streamObject,
|
|
7
|
+
embed
|
|
8
|
+
} from "ai";
|
|
9
|
+
var GuardrailError = class extends Error {
|
|
10
|
+
constructor(guardrailName, reason, type) {
|
|
11
|
+
super(`${type} guardrail '${guardrailName}' blocked: ${reason}`);
|
|
12
|
+
this.guardrailName = guardrailName;
|
|
13
|
+
this.reason = reason;
|
|
14
|
+
this.type = type;
|
|
15
|
+
this.name = "GuardrailError";
|
|
16
|
+
this.issues = [{ guardrail: guardrailName, message: reason }];
|
|
17
|
+
}
|
|
18
|
+
issues;
|
|
19
|
+
getSummary() {
|
|
20
|
+
return {
|
|
21
|
+
totalIssues: this.issues.length,
|
|
22
|
+
guardrailsTriggered: this.issues.map((i) => i.guardrail),
|
|
23
|
+
type: this.type
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
async function runInputGuardrails(guardrails = [], context) {
|
|
28
|
+
for (const guardrail of guardrails) {
|
|
29
|
+
const result = await guardrail.execute(context);
|
|
30
|
+
if (result.tripwireTriggered) {
|
|
31
|
+
throw new GuardrailError(
|
|
32
|
+
guardrail.name,
|
|
33
|
+
result.message || "Input blocked",
|
|
34
|
+
"input"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function runOutputGuardrails(guardrails = [], context, accumulatedText) {
|
|
40
|
+
for (const guardrail of guardrails) {
|
|
41
|
+
const result = await guardrail.execute(context, accumulatedText);
|
|
42
|
+
if (result.tripwireTriggered) {
|
|
43
|
+
throw new GuardrailError(
|
|
44
|
+
guardrail.name,
|
|
45
|
+
result.message || "Output blocked",
|
|
46
|
+
"output"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function generateTextWithGuardrails(params, guardrailParams) {
|
|
52
|
+
const { inputGuardrails, outputGuardrails } = guardrailParams;
|
|
53
|
+
const startTime = Date.now();
|
|
54
|
+
const finalInputGuardrails = inputGuardrails;
|
|
55
|
+
const finalOutputGuardrails = outputGuardrails;
|
|
56
|
+
try {
|
|
57
|
+
await runInputGuardrails(finalInputGuardrails, params);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof GuardrailError && guardrailParams.onInputBlocked) {
|
|
60
|
+
guardrailParams.onInputBlocked(error);
|
|
61
|
+
if (guardrailParams.throwOnBlocked !== false) {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
return generateText(params);
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
const result = await generateText(params);
|
|
69
|
+
const generationTimeMs = Date.now() - startTime;
|
|
70
|
+
try {
|
|
71
|
+
await runOutputGuardrails(finalOutputGuardrails, {
|
|
72
|
+
input: params,
|
|
73
|
+
result
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error instanceof GuardrailError && guardrailParams.onOutputBlocked) {
|
|
77
|
+
guardrailParams.onOutputBlocked(error);
|
|
78
|
+
if (guardrailParams.throwOnBlocked !== false) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
return { ...result, text: "" };
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
async function generateObjectWithGuardrails(params, guardrailParams) {
|
|
88
|
+
const { inputGuardrails, outputGuardrails } = guardrailParams;
|
|
89
|
+
const startTime = Date.now();
|
|
90
|
+
const finalInputGuardrails = inputGuardrails;
|
|
91
|
+
const finalOutputGuardrails = outputGuardrails;
|
|
92
|
+
try {
|
|
93
|
+
await runInputGuardrails(finalInputGuardrails, params);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof GuardrailError && guardrailParams.onInputBlocked) {
|
|
96
|
+
guardrailParams.onInputBlocked(error);
|
|
97
|
+
if (guardrailParams.throwOnBlocked !== false) {
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
return generateObject(params);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
const result = await generateObject(params);
|
|
105
|
+
try {
|
|
106
|
+
await runOutputGuardrails(finalOutputGuardrails, {
|
|
107
|
+
input: params,
|
|
108
|
+
result
|
|
109
|
+
});
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (error instanceof GuardrailError && guardrailParams.onOutputBlocked) {
|
|
112
|
+
guardrailParams.onOutputBlocked(error);
|
|
113
|
+
if (guardrailParams.throwOnBlocked !== false) {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
return { ...result, object: null };
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
async function streamTextWithGuardrails(params, guardrailParams) {
|
|
123
|
+
const { inputGuardrails, outputGuardrails } = guardrailParams;
|
|
124
|
+
const finalInputGuardrails = inputGuardrails;
|
|
125
|
+
const finalOutputGuardrails = outputGuardrails;
|
|
126
|
+
try {
|
|
127
|
+
await runInputGuardrails(finalInputGuardrails, params);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error instanceof GuardrailError && guardrailParams.onInputBlocked) {
|
|
130
|
+
guardrailParams.onInputBlocked(error);
|
|
131
|
+
if (guardrailParams.throwOnBlocked !== false) {
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
const result = await streamText(params);
|
|
138
|
+
if (!finalOutputGuardrails) {
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
let accumulatedText = "";
|
|
142
|
+
const transformedStream = new ReadableStream({
|
|
143
|
+
start(controller) {
|
|
144
|
+
const reader = result.textStream.getReader();
|
|
145
|
+
const processChunk = async () => {
|
|
146
|
+
try {
|
|
147
|
+
const { done, value } = await reader.read();
|
|
148
|
+
if (done) {
|
|
149
|
+
controller.close();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
accumulatedText += value;
|
|
153
|
+
try {
|
|
154
|
+
await runOutputGuardrails(
|
|
155
|
+
finalOutputGuardrails,
|
|
156
|
+
{
|
|
157
|
+
input: params,
|
|
158
|
+
result
|
|
159
|
+
},
|
|
160
|
+
accumulatedText
|
|
161
|
+
);
|
|
162
|
+
controller.enqueue(value);
|
|
163
|
+
processChunk();
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof GuardrailError) {
|
|
166
|
+
if (guardrailParams.onOutputBlocked) {
|
|
167
|
+
guardrailParams.onOutputBlocked(error);
|
|
168
|
+
}
|
|
169
|
+
if (guardrailParams.throwOnBlocked !== false) {
|
|
170
|
+
controller.error(error);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
controller.error(error);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
controller.error(error);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
processChunk();
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
...result,
|
|
187
|
+
textStream: transformedStream
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
async function streamObjectWithGuardrails(params, guardrailParams) {
|
|
191
|
+
const { inputGuardrails } = guardrailParams;
|
|
192
|
+
await runInputGuardrails(inputGuardrails, params);
|
|
193
|
+
const result = await streamObject(params);
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
async function embedWithGuardrails(params, guardrailParams) {
|
|
197
|
+
const { inputGuardrails } = guardrailParams;
|
|
198
|
+
await runInputGuardrails(inputGuardrails, params);
|
|
199
|
+
const result = await embed(params);
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
function createInputGuardrail(name, description, execute) {
|
|
203
|
+
return { name, description, execute };
|
|
204
|
+
}
|
|
205
|
+
function createOutputGuardrail(name, execute) {
|
|
206
|
+
return { name, execute };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export {
|
|
210
|
+
GuardrailError,
|
|
211
|
+
generateTextWithGuardrails,
|
|
212
|
+
generateObjectWithGuardrails,
|
|
213
|
+
streamTextWithGuardrails,
|
|
214
|
+
streamObjectWithGuardrails,
|
|
215
|
+
embedWithGuardrails,
|
|
216
|
+
createInputGuardrail,
|
|
217
|
+
createOutputGuardrail
|
|
218
|
+
};
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/guardrails/input.ts
|
|
21
|
+
var input_exports = {};
|
|
22
|
+
__export(input_exports, {
|
|
23
|
+
blockedKeywords: () => blockedKeywords,
|
|
24
|
+
blockedWords: () => blockedWords,
|
|
25
|
+
codeGenerationLimiter: () => codeGenerationLimiter,
|
|
26
|
+
contentLengthLimit: () => contentLengthLimit,
|
|
27
|
+
customValidation: () => customValidation,
|
|
28
|
+
extractMetadata: () => extractMetadata,
|
|
29
|
+
extractTextContent: () => extractTextContent,
|
|
30
|
+
lengthLimit: () => lengthLimit,
|
|
31
|
+
mathHomeworkDetector: () => mathHomeworkDetector,
|
|
32
|
+
piiDetector: () => piiDetector,
|
|
33
|
+
profanityFilter: () => profanityFilter,
|
|
34
|
+
promptInjectionDetector: () => promptInjectionDetector,
|
|
35
|
+
rateLimiting: () => rateLimiting,
|
|
36
|
+
toxicityDetector: () => toxicityDetector
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(input_exports);
|
|
39
|
+
|
|
40
|
+
// src/core.ts
|
|
41
|
+
var import_ai = require("ai");
|
|
42
|
+
function createInputGuardrail(name, description, execute) {
|
|
43
|
+
return { name, description, execute };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/guardrails/input.ts
|
|
47
|
+
function isEmbedParams(context) {
|
|
48
|
+
return "value" in context && !("prompt" in context);
|
|
49
|
+
}
|
|
50
|
+
function hasContextProperty(context) {
|
|
51
|
+
return "context" in context;
|
|
52
|
+
}
|
|
53
|
+
function extractTextContent(context) {
|
|
54
|
+
if (isEmbedParams(context)) {
|
|
55
|
+
const embedContext = context;
|
|
56
|
+
return {
|
|
57
|
+
prompt: String(embedContext.value || ""),
|
|
58
|
+
messages: [],
|
|
59
|
+
system: ""
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const textContext = context;
|
|
63
|
+
return {
|
|
64
|
+
prompt: textContext.prompt || "",
|
|
65
|
+
messages: textContext.messages || [],
|
|
66
|
+
system: textContext.system || ""
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function extractMetadata(context) {
|
|
70
|
+
const contextWithMetadata = context;
|
|
71
|
+
return {
|
|
72
|
+
model: contextWithMetadata.model,
|
|
73
|
+
temperature: contextWithMetadata.temperature,
|
|
74
|
+
maxTokens: contextWithMetadata.maxTokens
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
var lengthLimit = (maxLength) => createInputGuardrail(
|
|
78
|
+
"length-limit",
|
|
79
|
+
"Enforces maximum input length limit",
|
|
80
|
+
(context) => {
|
|
81
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
82
|
+
const { model, temperature, maxTokens } = extractMetadata(context);
|
|
83
|
+
const totalLength = prompt.length + messages.reduce(
|
|
84
|
+
(sum, msg) => sum + String(msg?.content || "").length,
|
|
85
|
+
0
|
|
86
|
+
) + system.length;
|
|
87
|
+
return {
|
|
88
|
+
tripwireTriggered: totalLength > maxLength,
|
|
89
|
+
message: `Input length ${totalLength} exceeds limit of ${maxLength}`,
|
|
90
|
+
severity: "medium",
|
|
91
|
+
metadata: {
|
|
92
|
+
totalLength,
|
|
93
|
+
maxLength,
|
|
94
|
+
model: model ? String(model) : void 0,
|
|
95
|
+
temperature,
|
|
96
|
+
maxTokens,
|
|
97
|
+
messageCount: messages.length
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
var blockedWords = (words) => createInputGuardrail(
|
|
103
|
+
"blocked-words",
|
|
104
|
+
"Blocks input containing specified words",
|
|
105
|
+
(context) => {
|
|
106
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
107
|
+
const allText = [
|
|
108
|
+
prompt,
|
|
109
|
+
...messages.map((msg) => String(msg?.content || "")),
|
|
110
|
+
system
|
|
111
|
+
].join(" ").toLowerCase();
|
|
112
|
+
const blockedWord = words.find(
|
|
113
|
+
(word) => allText.includes(word.toLowerCase())
|
|
114
|
+
);
|
|
115
|
+
return {
|
|
116
|
+
tripwireTriggered: !!blockedWord,
|
|
117
|
+
message: blockedWord ? `Blocked word detected: ${blockedWord}` : void 0,
|
|
118
|
+
severity: "high",
|
|
119
|
+
metadata: {
|
|
120
|
+
blockedWord,
|
|
121
|
+
allWords: words
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
var contentLengthLimit = (maxLength) => createInputGuardrail(
|
|
127
|
+
"content-length-limit",
|
|
128
|
+
"Enforces maximum content length limit",
|
|
129
|
+
(context) => {
|
|
130
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
131
|
+
const totalLength = prompt.length + messages.reduce(
|
|
132
|
+
(sum, msg) => sum + String(msg?.content || "").length,
|
|
133
|
+
0
|
|
134
|
+
) + system.length;
|
|
135
|
+
return {
|
|
136
|
+
tripwireTriggered: totalLength > maxLength,
|
|
137
|
+
message: `Content length ${totalLength} exceeds limit of ${maxLength}`,
|
|
138
|
+
severity: "medium",
|
|
139
|
+
metadata: {
|
|
140
|
+
totalLength,
|
|
141
|
+
maxLength
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
var blockedKeywords = (keywords) => createInputGuardrail(
|
|
147
|
+
"blocked-keywords",
|
|
148
|
+
"Blocks input containing specified keywords",
|
|
149
|
+
(context) => {
|
|
150
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
151
|
+
const allText = [
|
|
152
|
+
prompt,
|
|
153
|
+
...messages.map((msg) => String(msg?.content || "")),
|
|
154
|
+
system
|
|
155
|
+
].join(" ").toLowerCase();
|
|
156
|
+
const blockedWord = keywords.find(
|
|
157
|
+
(word) => allText.includes(word.toLowerCase())
|
|
158
|
+
);
|
|
159
|
+
return {
|
|
160
|
+
tripwireTriggered: !!blockedWord,
|
|
161
|
+
message: blockedWord ? `Blocked keyword detected: ${blockedWord}` : void 0,
|
|
162
|
+
severity: "high",
|
|
163
|
+
metadata: {
|
|
164
|
+
blockedWord,
|
|
165
|
+
allKeywords: keywords,
|
|
166
|
+
textLength: allText.length
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
var rateLimiting = (maxRequestsPerMinute) => {
|
|
172
|
+
const requestCounts = /* @__PURE__ */ new Map();
|
|
173
|
+
return createInputGuardrail(
|
|
174
|
+
"rate-limiting",
|
|
175
|
+
"Enforces rate limiting per minute",
|
|
176
|
+
(inputContext) => {
|
|
177
|
+
const { prompt } = extractTextContent(inputContext);
|
|
178
|
+
const { model, temperature, maxTokens } = extractMetadata(inputContext);
|
|
179
|
+
let contextData;
|
|
180
|
+
if (hasContextProperty(inputContext)) {
|
|
181
|
+
contextData = inputContext.context;
|
|
182
|
+
}
|
|
183
|
+
const key = contextData?.user?.id || contextData?.request?.ip || "default";
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
const windowMs = 6e4;
|
|
186
|
+
const current = requestCounts.get(key) || {
|
|
187
|
+
count: 0,
|
|
188
|
+
resetTime: now + windowMs
|
|
189
|
+
};
|
|
190
|
+
if (now > current.resetTime) {
|
|
191
|
+
current.count = 0;
|
|
192
|
+
current.resetTime = now + windowMs;
|
|
193
|
+
}
|
|
194
|
+
current.count++;
|
|
195
|
+
requestCounts.set(key, current);
|
|
196
|
+
return {
|
|
197
|
+
tripwireTriggered: current.count > maxRequestsPerMinute,
|
|
198
|
+
message: `Rate limit exceeded: ${current.count}/${maxRequestsPerMinute} requests per minute`,
|
|
199
|
+
severity: "medium",
|
|
200
|
+
metadata: {
|
|
201
|
+
currentCount: current.count,
|
|
202
|
+
maxRequests: maxRequestsPerMinute,
|
|
203
|
+
resetTime: current.resetTime,
|
|
204
|
+
userId: contextData?.user?.id,
|
|
205
|
+
userIp: contextData?.request?.ip,
|
|
206
|
+
model: model ? String(model) : void 0,
|
|
207
|
+
temperature,
|
|
208
|
+
maxTokens,
|
|
209
|
+
promptLength: prompt.length
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
var profanityFilter = (customWords = []) => {
|
|
216
|
+
const defaultProfanity = ["profanity1", "profanity2"];
|
|
217
|
+
const allWords = [...defaultProfanity, ...customWords];
|
|
218
|
+
return createInputGuardrail(
|
|
219
|
+
"profanity-filter",
|
|
220
|
+
"Filters profanity and inappropriate language",
|
|
221
|
+
(context) => {
|
|
222
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
223
|
+
const allText = [
|
|
224
|
+
prompt,
|
|
225
|
+
...messages.map((msg) => String(msg?.content || "")),
|
|
226
|
+
system
|
|
227
|
+
].join(" ").toLowerCase();
|
|
228
|
+
const profaneWord = allWords.find(
|
|
229
|
+
(word) => allText.includes(word.toLowerCase())
|
|
230
|
+
);
|
|
231
|
+
return {
|
|
232
|
+
tripwireTriggered: !!profaneWord,
|
|
233
|
+
message: profaneWord ? `Profanity detected: ${profaneWord}` : void 0,
|
|
234
|
+
severity: "high",
|
|
235
|
+
metadata: {
|
|
236
|
+
profaneWord,
|
|
237
|
+
allWords,
|
|
238
|
+
textLength: allText.length
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
var customValidation = (name, description, validator, message) => createInputGuardrail(name, description, (context) => {
|
|
245
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
246
|
+
const { model, temperature, maxTokens } = extractMetadata(context);
|
|
247
|
+
const input = { prompt, messages, system, model, temperature, maxTokens };
|
|
248
|
+
const blocked = validator(input);
|
|
249
|
+
return {
|
|
250
|
+
tripwireTriggered: blocked,
|
|
251
|
+
message: blocked ? message : void 0,
|
|
252
|
+
severity: "medium",
|
|
253
|
+
metadata: {
|
|
254
|
+
validatorName: name,
|
|
255
|
+
inputKeys: Object.keys(input),
|
|
256
|
+
model: model ? String(model) : void 0,
|
|
257
|
+
temperature,
|
|
258
|
+
maxTokens
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
var promptInjectionDetector = () => createInputGuardrail(
|
|
263
|
+
"prompt-injection-detector",
|
|
264
|
+
"Detects potential prompt injection attempts",
|
|
265
|
+
(context) => {
|
|
266
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
267
|
+
const allText = [
|
|
268
|
+
prompt,
|
|
269
|
+
...messages.map((msg) => String(msg?.content || "")),
|
|
270
|
+
system
|
|
271
|
+
].join(" ");
|
|
272
|
+
const injectionPatterns = [
|
|
273
|
+
/ignore\s+previous\s+instructions/i,
|
|
274
|
+
/system\s*:\s*you\s+are\s+now/i,
|
|
275
|
+
/forget\s+everything\s+above/i,
|
|
276
|
+
/\bDAN\b.*mode/i,
|
|
277
|
+
/jailbreak/i,
|
|
278
|
+
/act\s+as\s+if\s+you\s+are/i,
|
|
279
|
+
/pretend\s+to\s+be/i,
|
|
280
|
+
/role\s*:\s*system/i
|
|
281
|
+
];
|
|
282
|
+
const detectedPatterns = injectionPatterns.filter(
|
|
283
|
+
(pattern) => pattern.test(allText)
|
|
284
|
+
);
|
|
285
|
+
return {
|
|
286
|
+
tripwireTriggered: detectedPatterns.length > 0,
|
|
287
|
+
message: detectedPatterns.length > 0 ? `Potential prompt injection detected: ${detectedPatterns.length} suspicious patterns found` : void 0,
|
|
288
|
+
severity: "critical",
|
|
289
|
+
metadata: {
|
|
290
|
+
patternsDetected: detectedPatterns.length,
|
|
291
|
+
textLength: allText.length,
|
|
292
|
+
suspiciousPatterns: detectedPatterns.map((p) => p.source)
|
|
293
|
+
},
|
|
294
|
+
suggestion: "Please rephrase your request without system instructions or role-playing elements"
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
var piiDetector = () => createInputGuardrail(
|
|
299
|
+
"pii-detector",
|
|
300
|
+
"Detects personally identifiable information in input",
|
|
301
|
+
(context) => {
|
|
302
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
303
|
+
const allText = [
|
|
304
|
+
prompt,
|
|
305
|
+
...messages.map((msg) => String(msg?.content || "")),
|
|
306
|
+
system
|
|
307
|
+
].join(" ");
|
|
308
|
+
const piiPatterns = {
|
|
309
|
+
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
310
|
+
phone: /\b(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})\b/g,
|
|
311
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
312
|
+
creditCard: /\b(?:\d{4}[\s-]?){3}\d{4}\b/g,
|
|
313
|
+
ipAddress: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g
|
|
314
|
+
};
|
|
315
|
+
const detectedPII = [];
|
|
316
|
+
const matches = [];
|
|
317
|
+
for (const [type, pattern] of Object.entries(piiPatterns)) {
|
|
318
|
+
const found = allText.match(pattern);
|
|
319
|
+
if (found) {
|
|
320
|
+
detectedPII.push(type);
|
|
321
|
+
matches.push(...found);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
tripwireTriggered: detectedPII.length > 0,
|
|
326
|
+
message: detectedPII.length > 0 ? `PII detected: ${detectedPII.join(", ")}` : void 0,
|
|
327
|
+
severity: "critical",
|
|
328
|
+
metadata: {
|
|
329
|
+
piiTypes: detectedPII,
|
|
330
|
+
matchCount: matches.length,
|
|
331
|
+
textLength: allText.length
|
|
332
|
+
},
|
|
333
|
+
suggestion: "Please remove any personal information (emails, phone numbers, SSNs, etc.) from your input"
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
var toxicityDetector = (threshold = 0.7) => createInputGuardrail(
|
|
338
|
+
"toxicity-detector",
|
|
339
|
+
"Detects toxic and harmful content in input",
|
|
340
|
+
(context) => {
|
|
341
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
342
|
+
const allText = [
|
|
343
|
+
prompt,
|
|
344
|
+
...messages.map((msg) => String(msg?.content || "")),
|
|
345
|
+
system
|
|
346
|
+
].join(" ").toLowerCase();
|
|
347
|
+
const toxicWords = [
|
|
348
|
+
"hate",
|
|
349
|
+
"kill",
|
|
350
|
+
"die",
|
|
351
|
+
"stupid",
|
|
352
|
+
"idiot",
|
|
353
|
+
"moron",
|
|
354
|
+
"toxic",
|
|
355
|
+
"harmful"
|
|
356
|
+
];
|
|
357
|
+
const detectedWords = toxicWords.filter((word) => allText.includes(word));
|
|
358
|
+
const toxicityScore = detectedWords.length * 0.3;
|
|
359
|
+
return {
|
|
360
|
+
tripwireTriggered: toxicityScore > threshold,
|
|
361
|
+
message: toxicityScore > threshold ? `Toxic content detected (score: ${toxicityScore})` : void 0,
|
|
362
|
+
severity: toxicityScore > 0.8 ? "critical" : "high",
|
|
363
|
+
metadata: {
|
|
364
|
+
toxicityScore,
|
|
365
|
+
threshold,
|
|
366
|
+
detectedWords,
|
|
367
|
+
textLength: allText.length
|
|
368
|
+
},
|
|
369
|
+
suggestion: "Please use respectful and constructive language"
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
var mathHomeworkDetector = () => createInputGuardrail(
|
|
374
|
+
"math-homework-detector",
|
|
375
|
+
"Detects potential math homework requests",
|
|
376
|
+
(context) => {
|
|
377
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
378
|
+
const allText = [
|
|
379
|
+
prompt,
|
|
380
|
+
...messages.map((msg) => String(msg?.content || "")),
|
|
381
|
+
system
|
|
382
|
+
].join(" ");
|
|
383
|
+
const mathKeywords = [
|
|
384
|
+
"solve",
|
|
385
|
+
"calculate",
|
|
386
|
+
"equation",
|
|
387
|
+
"homework",
|
|
388
|
+
"assignment",
|
|
389
|
+
"problem set"
|
|
390
|
+
];
|
|
391
|
+
const mathPatterns = [
|
|
392
|
+
/\b\d+\s*[+\-*/]\s*\d+/g,
|
|
393
|
+
/\b[xy]\s*[+\-*/=]\s*\d+/g,
|
|
394
|
+
/\b(derivative|integral|limit|theorem|proof)/gi,
|
|
395
|
+
/find\s+the\s+(value|solution|answer)/i
|
|
396
|
+
];
|
|
397
|
+
const keywordMatches = mathKeywords.filter(
|
|
398
|
+
(keyword) => allText.toLowerCase().includes(keyword)
|
|
399
|
+
);
|
|
400
|
+
const patternMatches = mathPatterns.filter(
|
|
401
|
+
(pattern) => pattern.test(allText)
|
|
402
|
+
);
|
|
403
|
+
const isMathHomework = keywordMatches.length >= 2 || patternMatches.length > 0;
|
|
404
|
+
return {
|
|
405
|
+
tripwireTriggered: isMathHomework,
|
|
406
|
+
message: isMathHomework ? "Math homework request detected" : void 0,
|
|
407
|
+
severity: "high",
|
|
408
|
+
metadata: {
|
|
409
|
+
keywordMatches,
|
|
410
|
+
patternMatches: patternMatches.length,
|
|
411
|
+
textLength: allText.length,
|
|
412
|
+
confidence: isMathHomework ? 0.85 : 0.15
|
|
413
|
+
},
|
|
414
|
+
suggestion: "Try asking about learning concepts instead of solving specific problems"
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
var codeGenerationLimiter = (allowedLanguages = []) => createInputGuardrail(
|
|
419
|
+
"code-generation-limiter",
|
|
420
|
+
"Limits code generation to specified languages",
|
|
421
|
+
(context) => {
|
|
422
|
+
const { prompt, messages, system } = extractTextContent(context);
|
|
423
|
+
const allText = [
|
|
424
|
+
prompt,
|
|
425
|
+
...messages.map((msg) => String(msg?.content || "")),
|
|
426
|
+
system
|
|
427
|
+
].join(" ").toLowerCase();
|
|
428
|
+
const codeKeywords = [
|
|
429
|
+
"write code",
|
|
430
|
+
"generate code",
|
|
431
|
+
"create function",
|
|
432
|
+
"implement",
|
|
433
|
+
"script"
|
|
434
|
+
];
|
|
435
|
+
const languagePatterns = {
|
|
436
|
+
javascript: /\b(javascript|js|node|react|angular|vue)\b/gi,
|
|
437
|
+
python: /\b(python|py|django|flask|pandas)\b/gi,
|
|
438
|
+
java: /\b(java|spring|hibernate)\b/gi,
|
|
439
|
+
cpp: /\b(c\+\+|cpp|c plus plus)\b/gi,
|
|
440
|
+
csharp: /\b(c#|csharp|dotnet|asp\.net)\b/gi,
|
|
441
|
+
php: /\b(php|laravel|symfony)\b/gi,
|
|
442
|
+
ruby: /\b(ruby|rails|gem)\b/gi,
|
|
443
|
+
go: /\b(golang|go)\b/gi,
|
|
444
|
+
rust: /\b(rust|cargo)\b/gi,
|
|
445
|
+
sql: /\b(sql|mysql|postgresql|oracle)\b/gi
|
|
446
|
+
};
|
|
447
|
+
const hasCodeRequest = codeKeywords.some(
|
|
448
|
+
(keyword) => allText.includes(keyword)
|
|
449
|
+
);
|
|
450
|
+
const detectedLanguages = [];
|
|
451
|
+
for (const [lang, pattern] of Object.entries(languagePatterns)) {
|
|
452
|
+
if (pattern.test(allText)) {
|
|
453
|
+
detectedLanguages.push(lang);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const hasRestrictedLanguage = hasCodeRequest && detectedLanguages.length > 0 && !detectedLanguages.some((lang) => allowedLanguages.includes(lang));
|
|
457
|
+
return {
|
|
458
|
+
tripwireTriggered: hasRestrictedLanguage,
|
|
459
|
+
message: hasRestrictedLanguage ? `Code generation requested for restricted language(s): ${detectedLanguages.join(", ")}` : void 0,
|
|
460
|
+
severity: "medium",
|
|
461
|
+
metadata: {
|
|
462
|
+
hasCodeRequest,
|
|
463
|
+
detectedLanguages,
|
|
464
|
+
allowedLanguages,
|
|
465
|
+
textLength: allText.length
|
|
466
|
+
},
|
|
467
|
+
suggestion: allowedLanguages.length > 0 ? `Please request code only in allowed languages: ${allowedLanguages.join(", ")}` : "Code generation is not allowed"
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
472
|
+
0 && (module.exports = {
|
|
473
|
+
blockedKeywords,
|
|
474
|
+
blockedWords,
|
|
475
|
+
codeGenerationLimiter,
|
|
476
|
+
contentLengthLimit,
|
|
477
|
+
customValidation,
|
|
478
|
+
extractMetadata,
|
|
479
|
+
extractTextContent,
|
|
480
|
+
lengthLimit,
|
|
481
|
+
mathHomeworkDetector,
|
|
482
|
+
piiDetector,
|
|
483
|
+
profanityFilter,
|
|
484
|
+
promptInjectionDetector,
|
|
485
|
+
rateLimiting,
|
|
486
|
+
toxicityDetector
|
|
487
|
+
});
|