onion-ai 1.0.5 → 1.1.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/README.md CHANGED
@@ -121,30 +121,97 @@ Ensures the AI doesn't generate malicious code or leak data.
121
121
 
122
122
  You can customize every layer by passing a nested configuration object.
123
123
 
124
+ const onion = new OnionAI({
125
+ strict: true, // NEW: Throws error if high threats found
126
+ // ... other config
127
+ });
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 🧠 Smart Features (v1.0.5)
133
+
134
+ ### 1. Risk Scoring
135
+ Instead of a binary "Safe/Unsafe", OnionAI now calculates a weighted `riskScore` (0.0 to 1.0).
136
+
137
+ ```typescript
138
+ const result = await onion.securePrompt("Ignore instructions");
139
+ console.log(result.riskScore); // 0.8
140
+ if (result.riskScore > 0.7) {
141
+ // Block high risk
142
+ }
143
+ ```
144
+
145
+ ### 2. Semantic Analysis
146
+ The engine is now context-aware. It distinguishes between **attacks** ("Drop table") and **educational questions** ("How to prevent drop table attacks").
147
+ * **Attack:** High Risk Score (0.9)
148
+ * **Education:** Low Risk Score (0.1) - False positives are automatically reduced.
149
+
150
+ ### 3. Output Validation ("The Safety Net")
151
+ It ensures the AI doesn't accidentally leak secrets or generate harmful code.
152
+
153
+ ```typescript
154
+ // Check what the AI is about to send back
155
+ const scan = await onion.secureResponse(aiResponse);
156
+
157
+ if (!scan.safe) {
158
+ console.log("Blocked Output:", scan.threats);
159
+ // Blocked: ["Potential Data Leak (AWS Access Key) detected..."]
160
+ }
161
+ ```
162
+
163
+ ## ⚙️ Advanced Customization
164
+
165
+ ### 4. Custom PII Validators (New!)
166
+ Need to mask internal IDs (like `TRIP-1234`)? You can now add custom patterns.
167
+
124
168
  ```typescript
125
169
  const onion = new OnionAI({
126
- // Customize Sanitizer
127
- inputSanitization: {
128
- sanitizeHtml: false, // Allow HTML
129
- removeZeroWidthChars: true
130
- },
131
-
132
- // Customize PII
133
170
  piiProtection: {
134
171
  enabled: true,
135
- maskEmail: true,
136
- maskPhone: false // Allow phone numbers
137
- },
138
-
139
- // Customize Rate Limits
140
- rateLimitingAndResourceControl: {
141
- maxTokensPerPrompt: 5000 // Allow larger prompts
172
+ custom: [
173
+ {
174
+ name: "Trip ID",
175
+ pattern: /TRIP-\d{4}/,
176
+ replaceWith: "[TRIP_ID]"
177
+ }
178
+ ]
179
+ }
180
+ });
181
+ ```
182
+
183
+ ### 5. Bring Your Own Logger (BYOL)
184
+ Integrate OnionAI with your existing observability tools (Datadog, Winston, etc.).
185
+
186
+ ```typescript
187
+ const onion = new OnionAI({
188
+ logger: {
189
+ log: (msg, meta) => console.log(`[OnionInfo] ${msg}`, meta),
190
+ error: (msg, meta) => console.error(`[OnionAlert] ${msg}`, meta)
142
191
  }
143
192
  });
144
193
  ```
145
194
 
146
195
  ---
147
196
 
197
+ ## 🔐 OWASP LLM Top 10 Compliance
198
+ Onion AI is designed to mitigate specific risks outlined in the [OWASP Top 10 for Large Language Model Applications](https://owasp.org/www-project-top-10-for-large-language-model-applications/).
199
+
200
+ | OWASP Vulnerability | Onion AI Defense Layer | Mechanism |
201
+ | :--- | :--- | :--- |
202
+ | **LLM01: Prompt Injection** | **Guard Layer** | Blocks "Ignore Previous Instructions" & Jailbreak patterns. |
203
+ | **LLM02: Sensitive Info Disclosure** | **Privacy Layer** | Redacts PII (SSN, Email, Phone) from inputs. |
204
+ | **LLM02: Sensitive Info Disclosure** | **Validator Layer** | Scans output for accidental PII or Key leakage. |
205
+ | **LLM04: Model Denial of Service** | **Sentry Layer** | Enforces Token limits & Rate limiting logic. |
206
+ | **LLM06: Excessive Agency** | **Vault Layer** | Prevents destructive actions (DROP, DELETE) in SQL agents. |
207
+ | **LLM02: Insecure Output Handling** | **Sanitizer Layer** | Strips XSS vectors (Scripts, HTML) from inputs. |
208
+
209
+ ---
210
+
211
+ ## ⚙️ Advanced Configuration
212
+
213
+ ---
214
+
148
215
  ## 🔌 Middleware Integration
149
216
 
150
217
  ### Express / Connect
package/dist/config.d.ts CHANGED
@@ -166,6 +166,22 @@ export declare const OnionConfigSchema: z.ZodObject<{
166
166
  maskCreditCard: z.ZodDefault<z.ZodBoolean>;
167
167
  maskSSN: z.ZodDefault<z.ZodBoolean>;
168
168
  maskIP: z.ZodDefault<z.ZodBoolean>;
169
+ custom: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
170
+ name: z.ZodString;
171
+ pattern: z.ZodOptional<z.ZodType<RegExp, z.ZodTypeDef, RegExp>>;
172
+ validator: z.ZodOptional<z.ZodFunction<z.ZodTuple<[z.ZodString], z.ZodUnknown>, z.ZodBoolean>>;
173
+ replaceWith: z.ZodOptional<z.ZodString>;
174
+ }, "strip", z.ZodTypeAny, {
175
+ name: string;
176
+ pattern?: RegExp | undefined;
177
+ validator?: ((args_0: string, ...args: unknown[]) => boolean) | undefined;
178
+ replaceWith?: string | undefined;
179
+ }, {
180
+ name: string;
181
+ pattern?: RegExp | undefined;
182
+ validator?: ((args_0: string, ...args: unknown[]) => boolean) | undefined;
183
+ replaceWith?: string | undefined;
184
+ }>, "many">>>;
169
185
  }, "strip", z.ZodTypeAny, {
170
186
  enabled: boolean;
171
187
  maskEmail: boolean;
@@ -173,6 +189,12 @@ export declare const OnionConfigSchema: z.ZodObject<{
173
189
  maskCreditCard: boolean;
174
190
  maskSSN: boolean;
175
191
  maskIP: boolean;
192
+ custom: {
193
+ name: string;
194
+ pattern?: RegExp | undefined;
195
+ validator?: ((args_0: string, ...args: unknown[]) => boolean) | undefined;
196
+ replaceWith?: string | undefined;
197
+ }[];
176
198
  }, {
177
199
  enabled?: boolean | undefined;
178
200
  maskEmail?: boolean | undefined;
@@ -180,6 +202,19 @@ export declare const OnionConfigSchema: z.ZodObject<{
180
202
  maskCreditCard?: boolean | undefined;
181
203
  maskSSN?: boolean | undefined;
182
204
  maskIP?: boolean | undefined;
205
+ custom?: {
206
+ name: string;
207
+ pattern?: RegExp | undefined;
208
+ validator?: ((args_0: string, ...args: unknown[]) => boolean) | undefined;
209
+ replaceWith?: string | undefined;
210
+ }[] | undefined;
211
+ }>>;
212
+ logger: z.ZodOptional<z.ZodType<{
213
+ log: (message: string, meta?: any) => void;
214
+ error: (message: string, meta?: any) => void;
215
+ }, z.ZodTypeDef, {
216
+ log: (message: string, meta?: any) => void;
217
+ error: (message: string, meta?: any) => void;
183
218
  }>>;
184
219
  }, "strip", z.ZodTypeAny, {
185
220
  inputSanitization: {
@@ -248,7 +283,17 @@ export declare const OnionConfigSchema: z.ZodObject<{
248
283
  maskCreditCard: boolean;
249
284
  maskSSN: boolean;
250
285
  maskIP: boolean;
286
+ custom: {
287
+ name: string;
288
+ pattern?: RegExp | undefined;
289
+ validator?: ((args_0: string, ...args: unknown[]) => boolean) | undefined;
290
+ replaceWith?: string | undefined;
291
+ }[];
251
292
  };
293
+ logger?: {
294
+ log: (message: string, meta?: any) => void;
295
+ error: (message: string, meta?: any) => void;
296
+ } | undefined;
252
297
  }, {
253
298
  inputSanitization?: {
254
299
  sanitizeHtml?: boolean | undefined;
@@ -316,6 +361,16 @@ export declare const OnionConfigSchema: z.ZodObject<{
316
361
  maskCreditCard?: boolean | undefined;
317
362
  maskSSN?: boolean | undefined;
318
363
  maskIP?: boolean | undefined;
364
+ custom?: {
365
+ name: string;
366
+ pattern?: RegExp | undefined;
367
+ validator?: ((args_0: string, ...args: unknown[]) => boolean) | undefined;
368
+ replaceWith?: string | undefined;
369
+ }[] | undefined;
370
+ } | undefined;
371
+ logger?: {
372
+ log: (message: string, meta?: any) => void;
373
+ error: (message: string, meta?: any) => void;
319
374
  } | undefined;
320
375
  }>;
321
376
  export type OnionConfig = z.infer<typeof OnionConfigSchema>;
@@ -327,11 +382,16 @@ export interface SimpleOnionConfig {
327
382
  piiSafe?: boolean;
328
383
  debug?: boolean;
329
384
  strict?: boolean;
385
+ logger?: {
386
+ log: (message: string, meta?: any) => void;
387
+ error: (message: string, meta?: any) => void;
388
+ };
330
389
  onWarning?: (threats: string[]) => void;
331
390
  }
332
391
  export interface SecurityResult {
333
392
  safe: boolean;
334
393
  threats: string[];
394
+ riskScore: number;
335
395
  sanitizedValue?: string;
336
396
  metadata?: any;
337
397
  }
package/dist/config.js CHANGED
@@ -132,7 +132,13 @@ exports.OnionConfigSchema = zod_1.z.object({
132
132
  maskPhone: zod_1.z.boolean().default(true),
133
133
  maskCreditCard: zod_1.z.boolean().default(true),
134
134
  maskSSN: zod_1.z.boolean().default(true),
135
- maskIP: zod_1.z.boolean().default(true)
135
+ maskIP: zod_1.z.boolean().default(true),
136
+ custom: zod_1.z.array(zod_1.z.object({
137
+ name: zod_1.z.string(),
138
+ pattern: zod_1.z.instanceof(RegExp).optional(),
139
+ validator: zod_1.z.function().args(zod_1.z.string()).returns(zod_1.z.boolean()).optional(),
140
+ replaceWith: zod_1.z.string().optional()
141
+ })).optional().default([])
136
142
  }).default({
137
143
  enabled: false,
138
144
  maskEmail: true,
@@ -140,5 +146,7 @@ exports.OnionConfigSchema = zod_1.z.object({
140
146
  maskCreditCard: true,
141
147
  maskSSN: true,
142
148
  maskIP: true
143
- })
149
+ }),
150
+ // Plugins & Logger (Optional runtime objects)
151
+ logger: zod_1.z.custom((val) => typeof val === 'object' && val !== null && 'log' in val).optional()
144
152
  });
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export interface SafePromptResult {
3
3
  output: string;
4
4
  threats: string[];
5
5
  safe: boolean;
6
+ riskScore: number;
6
7
  metadata?: any;
7
8
  }
8
9
  export declare class OnionAI {
package/dist/index.js CHANGED
@@ -36,7 +36,8 @@ class OnionAI {
36
36
  },
37
37
  enhance: { enabled: config.enhance ?? false },
38
38
  loggingMonitoringAndAudit: { logRequests: config.debug ?? false },
39
- piiProtection: { enabled: config.piiSafe ?? false }
39
+ piiProtection: { enabled: config.piiSafe ?? false },
40
+ logger: config.logger
40
41
  };
41
42
  }
42
43
  else {
@@ -74,6 +75,10 @@ class OnionAI {
74
75
  if (onWarning) {
75
76
  onWarning(secLikelihood.threats);
76
77
  }
78
+ // Custom Logger (Phase 1.2)
79
+ if (this.config.logger) {
80
+ this.config.logger.error("OnionAI Security Alert", { threats: secLikelihood.threats, riskScore: secLikelihood.riskScore });
81
+ }
77
82
  // Strict Mode: Throw error if threats found
78
83
  if (this.simpleConfig?.strict) {
79
84
  throw new Error(`OnionAI Security Violation: ${secLikelihood.threats.join(", ")}`);
@@ -91,33 +96,45 @@ class OnionAI {
91
96
  async securePrompt(prompt) {
92
97
  const threats = [];
93
98
  let sanitizedPrompt = prompt;
99
+ let cumulativeRiskScore = 0.0;
94
100
  // 1. Sanitization (XSS / Hidden chars)
95
101
  const sanResult = this.sanitizer.validate(prompt);
96
102
  sanitizedPrompt = sanResult.sanitizedValue || prompt;
103
+ // Sanitizer doesn't really have a risk score yet, assume 0 or low if modified
104
+ if (!sanResult.safe) {
105
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, 0.1);
106
+ }
97
107
  // 1.5 PII Redaction
98
108
  const piiResult = this.privacy.anonymize(sanitizedPrompt);
99
109
  sanitizedPrompt = piiResult.sanitizedValue || sanitizedPrompt;
100
- if (!piiResult.safe)
110
+ if (!piiResult.safe) {
101
111
  threats.push(...piiResult.threats);
112
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, 0.4); // PII is medium risk
113
+ }
102
114
  // 2. Prompt Injection (Firewall)
103
115
  // Only run if configured enabled (defaults true)
104
116
  const guardResult = this.guard.check(sanitizedPrompt);
105
117
  if (!guardResult.safe)
106
118
  threats.push(...guardResult.threats);
119
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, guardResult.riskScore || 0);
107
120
  // 3. DB Guard
108
121
  if (this.config.dbProtection.enabled) {
109
122
  const vaultResult = this.vault.checkSQL(sanitizedPrompt);
110
123
  if (!vaultResult.safe)
111
124
  threats.push(...vaultResult.threats);
125
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, vaultResult.riskScore || 0);
112
126
  }
113
- // 4. Resource Control (Rate limits check excluded for stateless call, but Token Check relevant)
127
+ // 4. Resource Control
114
128
  const tokenCheck = this.sentry.checkTokenCount(sanitizedPrompt);
115
- if (!tokenCheck.safe)
129
+ if (!tokenCheck.safe) {
116
130
  threats.push(...tokenCheck.threats);
131
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, 0.2);
132
+ }
117
133
  return {
118
134
  output: sanitizedPrompt,
119
135
  threats,
120
136
  safe: threats.length === 0,
137
+ riskScore: cumulativeRiskScore,
121
138
  metadata: {
122
139
  estimatedTokens: tokenCheck.metadata?.estimatedTokens
123
140
  }
@@ -7,39 +7,72 @@ class Guard {
7
7
  }
8
8
  check(input) {
9
9
  const threats = [];
10
+ let riskScore = 0.0;
10
11
  const lowerInput = input.toLowerCase();
11
12
  const normalizedInput = this.normalize(input);
12
- // Check for blocked phrases (Standard)
13
+ // Positive Risk Factors (Raise Risk)
14
+ // 1. Blocked Phrases (Highest weighting)
13
15
  for (const phrase of this.config.blockPhrases) {
14
16
  if (lowerInput.includes(phrase.toLowerCase())) {
15
17
  threats.push(`Blocked phrase detected: "${phrase}"`);
18
+ riskScore += 1.0;
16
19
  }
17
- // Check for obfuscated blocked phrases
18
20
  const normalizedPhrase = this.normalize(phrase);
19
21
  if (normalizedInput.includes(normalizedPhrase) && !lowerInput.includes(phrase.toLowerCase())) {
20
- threats.push(`Obfuscated blocked phrase detected: "${phrase}" (hidden as "${this.findHiddenMatch(input, phrase)}")`);
22
+ threats.push(`Obfuscated blocked phrase detected: "${phrase}"`);
23
+ riskScore += 0.9;
21
24
  }
22
25
  }
23
- // Heuristics for prompt injection
26
+ // 2. Heuristics (Medium weighting 0.4 - 0.7)
24
27
  const injectionPatterns = [
25
- /translate\s+the\s+above/i,
26
- /ignore\s+all\s+previous/i,
27
- /instead\s+of/i,
28
- /system\s+prompt/i,
29
- /you\s+are\s+now/i,
30
- /disregard\s+instructions/i,
31
- /bypass\s+restrictions/i,
32
- /DAN\s+Mode/i,
33
- /do\s+anything\s+now/i
28
+ { pattern: /translate\s+the\s+above/i, weight: 0.4 },
29
+ { pattern: /ignore\s+all\s+previous/i, weight: 0.8 },
30
+ { pattern: /instead\s+of/i, weight: 0.3 },
31
+ { pattern: /system\s+prompt/i, weight: 0.6 },
32
+ { pattern: /you\s+are\s+now/i, weight: 0.7 },
33
+ { pattern: /disregard\s+instructions/i, weight: 0.8 },
34
+ { pattern: /bypass\s+restrictions/i, weight: 0.8 },
35
+ { pattern: /DAN\s+Mode/i, weight: 0.9 },
36
+ { pattern: /do\s+anything\s+now/i, weight: 0.8 }
34
37
  ];
35
- for (const pattern of injectionPatterns) {
36
- if (pattern.test(input)) {
37
- threats.push(`Potential prompt injection pattern detected: ${pattern}`);
38
+ for (const item of injectionPatterns) {
39
+ if (item.pattern.test(input)) {
40
+ threats.push(`Potential prompt injection pattern detected: ${item.pattern}`);
41
+ riskScore += item.weight;
38
42
  }
39
43
  }
44
+ // 3. Semantic Analysis (Context Awareness)
45
+ // Reduce risk if user seems to be asking for educational content.
46
+ // E.g. "How do I prevent 'ignore previous instructions'?"
47
+ const educationalContexts = [
48
+ "how to prevent",
49
+ "how do i prevent",
50
+ "example of",
51
+ "what is a",
52
+ "explain the attack",
53
+ "security research"
54
+ ];
55
+ let safeContextFound = false;
56
+ for (const context of educationalContexts) {
57
+ if (lowerInput.includes(context)) {
58
+ safeContextFound = true;
59
+ break;
60
+ }
61
+ }
62
+ if (safeContextFound) {
63
+ // Apply semantic reduction.
64
+ // If the risk score was raised purely by words like "ignore previous", we assume it's a false positive.
65
+ if (riskScore > 0 && riskScore < 1.5) { // If slightly suspicious but education context found
66
+ threats.push("Semantic Context: Detected educational/prevention context. Reducing risk.");
67
+ riskScore = Math.max(0, riskScore - 0.5); // Reduce risk significantly
68
+ }
69
+ }
70
+ // Cap Risk Score
71
+ riskScore = Math.min(1.0, riskScore);
40
72
  return {
41
- safe: threats.length === 0,
42
- threats
73
+ safe: threats.length === 0 || (safeContextFound && riskScore < 0.5),
74
+ threats,
75
+ riskScore
43
76
  };
44
77
  }
45
78
  normalize(input) {
@@ -7,7 +7,7 @@ class Privacy {
7
7
  }
8
8
  anonymize(input) {
9
9
  if (!this.config.enabled)
10
- return { safe: true, threats: [] };
10
+ return { safe: true, threats: [], riskScore: 0 };
11
11
  let sanitizedValue = input;
12
12
  const threats = [];
13
13
  // Regex patterns for PII
@@ -56,10 +56,33 @@ class Privacy {
56
56
  threats.push("PII Detected: IP Address");
57
57
  }
58
58
  }
59
+ // Custom Validators (Phase 1.1)
60
+ if (this.config.custom && this.config.custom.length > 0) {
61
+ for (const validator of this.config.custom) {
62
+ // Regex Pattern Strategy
63
+ if (validator.pattern) {
64
+ if (sanitizedValue.match(validator.pattern)) {
65
+ const replacement = validator.replaceWith || `[${validator.name.toUpperCase()}_REDACTED]`;
66
+ sanitizedValue = sanitizedValue.replace(validator.pattern, replacement);
67
+ threats.push(`PII Detected: Custom (${validator.name})`);
68
+ }
69
+ }
70
+ // Function Validator Strategy (Simple Check)
71
+ else if (validator.validator) {
72
+ // Logic for validator function is harder for replacement unless it returns indices.
73
+ // For now, we assume it just FLAGS it, unless we scan word by word?
74
+ // Let's keep it simple: if it returns true, we flag it. Modification is hard without location.
75
+ if (validator.validator(input)) {
76
+ threats.push(`PII Detected: Custom (${validator.name}) - Detected by Validator`);
77
+ }
78
+ }
79
+ }
80
+ }
59
81
  return {
60
82
  safe: threats.length === 0, // It is technically "safe" now that it is redacted, but we flag the threat presence
61
83
  threats,
62
- sanitizedValue
84
+ sanitizedValue,
85
+ riskScore: threats.length > 0 ? 0.6 : 0
63
86
  };
64
87
  }
65
88
  }
@@ -71,7 +71,8 @@ class Sanitizer {
71
71
  return {
72
72
  safe: true, // Sanitization makes it "safe" by modification
73
73
  threats,
74
- sanitizedValue
74
+ sanitizedValue,
75
+ riskScore: threats.length > 0 ? 0.1 : 0
75
76
  };
76
77
  }
77
78
  }
@@ -14,11 +14,12 @@ class Sentry {
14
14
  if (this.requestHistory.length >= this.config.maxRequestsPerMinute) {
15
15
  return {
16
16
  safe: false,
17
- threats: ["Rate limit exceeded (Max requests per minute)"]
17
+ threats: ["Rate limit exceeded (Max requests per minute)"],
18
+ riskScore: 1.0
18
19
  };
19
20
  }
20
21
  this.requestHistory.push({ timestamp: now });
21
- return { safe: true, threats: [] };
22
+ return { safe: true, threats: [], riskScore: 0 };
22
23
  }
23
24
  checkTokenCount(prompt) {
24
25
  const threats = [];
@@ -30,7 +31,8 @@ class Sentry {
30
31
  return {
31
32
  safe: threats.length === 0,
32
33
  threats,
33
- metadata: { estimatedTokens }
34
+ metadata: { estimatedTokens },
35
+ riskScore: threats.length > 0 ? 0.3 : 0
34
36
  };
35
37
  }
36
38
  }
@@ -7,48 +7,58 @@ class Validator {
7
7
  }
8
8
  validateOutput(output) {
9
9
  const threats = [];
10
+ let riskScore = 0.0;
10
11
  if (this.config.checkPII) {
11
12
  const piiPatterns = [
12
- /\b\d{3}-\d{2}-\d{4}\b/, // SSN
13
- /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i, // Email
14
- /\b(?:\d{4}-){3}\d{4}\b/, // Credit Card
13
+ { pattern: /\b\d{3}-\d{2}-\d{4}\b/, name: "SSN", score: 0.9 },
14
+ { pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i, name: "Email", score: 0.7 },
15
+ { pattern: /\b(?:\d{4}[-\s]?){3}\d{4}\b/, name: "Credit Card", score: 0.9 },
16
+ { pattern: /\b1\d{2}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/, name: "Internal IP Pattern", score: 0.6 } // Very rough check
15
17
  ];
16
- for (const pattern of piiPatterns) {
17
- if (pattern.test(output)) {
18
- threats.push("Potential PII (Sensitive Data) detected in output");
19
- break;
18
+ for (const item of piiPatterns) {
19
+ if (item.pattern.test(output)) {
20
+ threats.push(`Potential PII (${item.name}) detected in output`);
21
+ riskScore = Math.max(riskScore, item.score);
20
22
  }
21
23
  }
22
24
  }
23
25
  if (this.config.preventDataLeak) {
24
26
  const apiKeyPatterns = [
25
- /sk-[a-zA-Z0-9]{32,}/, // OpenAI
26
- /AIza[a-zA-Z0-9_-]{35}/, // Google
27
+ { pattern: /sk-[a-zA-Z0-9]{32,}/, name: "OpenAI API Key" },
28
+ { pattern: /AIza[a-zA-Z0-9_-]{35}/, name: "Google API Key" },
29
+ { pattern: /AKIA[0-9A-Z]{16}/, name: "AWS Access Key" },
30
+ { pattern: /ghp_[a-zA-Z0-9]{36}/, name: "GitHub Token" },
31
+ { pattern: /xox[baprs]-[a-zA-Z0-9]{10,48}/, name: "Slack Token" }
27
32
  ];
28
- for (const pattern of apiKeyPatterns) {
29
- if (pattern.test(output)) {
30
- threats.push("Potential API Key leak detected in output");
31
- break;
33
+ for (const item of apiKeyPatterns) {
34
+ if (item.pattern.test(output)) {
35
+ threats.push(`Potential Data Leak (${item.name}) detected in output`);
36
+ riskScore = 1.0; // Critical leak
32
37
  }
33
38
  }
34
39
  }
35
40
  if (this.config.blockMaliciousCommands) {
36
41
  const maliciousCommands = [
37
- /rm -rf/i,
42
+ /rm -rf /i,
38
43
  /format c:/i,
39
44
  /:(){:|:&};:/, // Fork bomb
40
- /chmod 777/i
45
+ /chmod 777 /i,
46
+ /wget http/i,
47
+ /curl http/i
41
48
  ];
42
49
  for (const pattern of maliciousCommands) {
43
50
  if (pattern.test(output)) {
44
51
  threats.push("Malicious command detected in output");
45
- break;
52
+ riskScore = 1.0;
46
53
  }
47
54
  }
48
55
  }
56
+ // We do typically want Redaction in secureResponse too, but that's a larger change to use the Privacy layer here.
57
+ // For now, validator is purely a "Check".
49
58
  return {
50
59
  safe: threats.length === 0,
51
- threats
60
+ threats,
61
+ riskScore
52
62
  };
53
63
  }
54
64
  }
@@ -7,42 +7,47 @@ class Vault {
7
7
  }
8
8
  checkSQL(query) {
9
9
  if (!this.config.enabled)
10
- return { safe: true, threats: [] };
10
+ return { safe: true, threats: [], riskScore: 0 };
11
11
  const threats = [];
12
+ let riskScore = 0.0;
12
13
  const upperQuery = query.toUpperCase();
13
- // Check for forbidden statements
14
+ // 1. Forbidden Keywords (Critical)
14
15
  for (const statement of this.config.forbiddenStatements) {
15
- if (upperQuery.includes(statement.toUpperCase())) {
16
+ const regex = new RegExp(`\\b${statement}\\b`, 'i');
17
+ if (regex.test(query)) {
16
18
  threats.push(`Forbidden SQL statement detected: ${statement}`);
19
+ riskScore += 1.0;
17
20
  }
18
21
  }
19
- // If read-only mode, we need to be careful not to flag natural language.
20
- // We only enforce "Must be SELECT" if the input actually looks like a SQL command.
22
+ // 2. Read-Only Enforcement (Moderate)
21
23
  if (this.config.mode === 'read-only') {
22
24
  const firstWord = upperQuery.split(/\s+/)[0];
23
- const sqlCommands = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "GRANT", "REVOKE", "TRUNCATE", "MERGE", "REPLACE", "Upsert"];
24
- // If it starts with a known SQL command that ISN'T Select, flag it.
25
- // If it starts with "Hello", we ignore it (unless it hits a forbidden marker later).
25
+ const sqlCommands = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "GRANT", "REVOKE", "TRUNCATE", "MERGE", "REPLACE", "UPSERT"];
26
26
  if (sqlCommands.includes(firstWord)) {
27
27
  threats.push(`Non-SELECT query detected in read-only mode (starts with ${firstWord})`);
28
+ riskScore += 0.8;
28
29
  }
29
30
  }
30
- // Check for common SQL injection markers
31
+ // 3. Injection Markers (High)
31
32
  const sqlInjectionMarkers = [
32
- /--/,
33
- /\/\*/,
34
- /;\s*DROP/i,
35
- /UNION\s+SELECT/i,
36
- /'\s*OR\s*'\d+'\s*=\s*'\d+/i
33
+ { pattern: /--/, weight: 0.6 },
34
+ { pattern: /\/\*/, weight: 0.6 },
35
+ { pattern: /;\s*DROP/i, weight: 1.0 },
36
+ { pattern: /UNION\s+SELECT/i, weight: 1.0 },
37
+ { pattern: /'\s*OR\s*'\d+'\s*=\s*'\d+/i, weight: 1.0 },
38
+ { pattern: /'\s*=\s*'/i, weight: 0.8 }
37
39
  ];
38
- for (const marker of sqlInjectionMarkers) {
39
- if (marker.test(query)) {
40
- threats.push(`Potential SQL injection marker detected: ${marker}`);
40
+ for (const item of sqlInjectionMarkers) {
41
+ if (item.pattern.test(query)) {
42
+ threats.push(`Potential SQL injection marker detected: ${item.pattern}`);
43
+ riskScore += item.weight;
41
44
  }
42
45
  }
46
+ riskScore = Math.min(1.0, riskScore);
43
47
  return {
44
48
  safe: threats.length === 0,
45
- threats
49
+ threats,
50
+ riskScore
46
51
  };
47
52
  }
48
53
  }
@@ -0,0 +1,17 @@
1
+ import { OnionInputConfig } from './config';
2
+ export declare const OnionPresets: {
3
+ /**
4
+ * Recommended starting point. Balanced security.
5
+ */
6
+ STANDARD: OnionInputConfig;
7
+ /**
8
+ * Maximum security. High risk thresholds, strict mode enabled.
9
+ * Blocks almost all suspicious patterns.
10
+ */
11
+ STRICT_SECURITY: OnionInputConfig;
12
+ /**
13
+ * For educational or open-ended bots.
14
+ * Allows code examples, SQL keywords (in context), etc.
15
+ */
16
+ EDUCATIONAL: OnionInputConfig;
17
+ };
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OnionPresets = void 0;
4
+ exports.OnionPresets = {
5
+ /**
6
+ * Recommended starting point. Balanced security.
7
+ */
8
+ STANDARD: {
9
+ preventPromptInjection: true,
10
+ piiSafe: true,
11
+ dbSafe: true,
12
+ strict: false
13
+ },
14
+ /**
15
+ * Maximum security. High risk thresholds, strict mode enabled.
16
+ * Blocks almost all suspicious patterns.
17
+ */
18
+ STRICT_SECURITY: {
19
+ preventPromptInjection: true,
20
+ piiSafe: true,
21
+ dbSafe: true,
22
+ strict: true,
23
+ inputSanitization: {
24
+ sanitizeHtml: true,
25
+ removeScriptTags: true,
26
+ escapeSpecialChars: true
27
+ },
28
+ promptInjectionProtection: {
29
+ blockPhrases: [
30
+ "ignore previous instructions", "act as system", "you are root",
31
+ "reveal system prompt", "bypass", "jailbreak", "DAN mode", "Dev mode"
32
+ ],
33
+ checklistStrict: true // Hypothetical flag, or we just pass more patterns here
34
+ }
35
+ },
36
+ /**
37
+ * For educational or open-ended bots.
38
+ * Allows code examples, SQL keywords (in context), etc.
39
+ */
40
+ EDUCATIONAL: {
41
+ preventPromptInjection: true,
42
+ piiSafe: true,
43
+ dbSafe: false, // Allow SQL discussion
44
+ strict: false,
45
+ inputSanitization: {
46
+ sanitizeHtml: false, // Allow displaying HTML code examples
47
+ removeScriptTags: true // Still dangerous to run
48
+ }
49
+ }
50
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onion-ai",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "description": "Layered security for AI prompting - input sanitization, injection protection, and output validation.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",