onion-ai 1.0.4 → 1.0.6

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
@@ -8,6 +8,7 @@ Think of it as **[Helmet](https://helmetjs.github.io/) for LLMs**.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/onion-ai.svg?style=flat-square)](https://www.npmjs.com/package/onion-ai)
10
10
  [![license](https://img.shields.io/npm/l/onion-ai.svg?style=flat-square)](https://github.com/himanshu-mamgain/onion-ai/blob/main/LICENSE)
11
+ [![Documentation](https://img.shields.io/badge/docs-onion--ai-8b5cf6?style=flat-square&logo=github)](https://himanshu-mamgain.github.io/onion-ai/)
11
12
 
12
13
  ---
13
14
 
@@ -120,30 +121,51 @@ Ensures the AI doesn't generate malicious code or leak data.
120
121
 
121
122
  You can customize every layer by passing a nested configuration object.
122
123
 
123
- ```typescript
124
124
  const onion = new OnionAI({
125
- // Customize Sanitizer
126
- inputSanitization: {
127
- sanitizeHtml: false, // Allow HTML
128
- removeZeroWidthChars: true
129
- },
130
-
131
- // Customize PII
132
- piiProtection: {
133
- enabled: true,
134
- maskEmail: true,
135
- maskPhone: false // Allow phone numbers
136
- },
137
-
138
- // Customize Rate Limits
139
- rateLimitingAndResourceControl: {
140
- maxTokensPerPrompt: 5000 // Allow larger prompts
141
- }
125
+ strict: true, // NEW: Throws error if high threats found
126
+ // ... other config
142
127
  });
143
128
  ```
144
129
 
145
130
  ---
146
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
+ ---
164
+
165
+ ## ⚙️ Advanced Configuration
166
+
167
+ ---
168
+
147
169
  ## 🔌 Middleware Integration
148
170
 
149
171
  ### Express / Connect
package/dist/config.d.ts CHANGED
@@ -326,11 +326,13 @@ export interface SimpleOnionConfig {
326
326
  enhance?: boolean;
327
327
  piiSafe?: boolean;
328
328
  debug?: boolean;
329
+ strict?: boolean;
329
330
  onWarning?: (threats: string[]) => void;
330
331
  }
331
332
  export interface SecurityResult {
332
333
  safe: boolean;
333
334
  threats: string[];
335
+ riskScore: number;
334
336
  sanitizedValue?: string;
335
337
  metadata?: any;
336
338
  }
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
@@ -74,6 +74,10 @@ class OnionAI {
74
74
  if (onWarning) {
75
75
  onWarning(secLikelihood.threats);
76
76
  }
77
+ // Strict Mode: Throw error if threats found
78
+ if (this.simpleConfig?.strict) {
79
+ throw new Error(`OnionAI Security Violation: ${secLikelihood.threats.join(", ")}`);
80
+ }
77
81
  }
78
82
  // 2. Enhance (if enabled)
79
83
  // We always try to enhance the output we have, even if it had warnings (as long as it wasn't empty)
@@ -87,33 +91,45 @@ class OnionAI {
87
91
  async securePrompt(prompt) {
88
92
  const threats = [];
89
93
  let sanitizedPrompt = prompt;
94
+ let cumulativeRiskScore = 0.0;
90
95
  // 1. Sanitization (XSS / Hidden chars)
91
96
  const sanResult = this.sanitizer.validate(prompt);
92
97
  sanitizedPrompt = sanResult.sanitizedValue || prompt;
98
+ // Sanitizer doesn't really have a risk score yet, assume 0 or low if modified
99
+ if (!sanResult.safe) {
100
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, 0.1);
101
+ }
93
102
  // 1.5 PII Redaction
94
103
  const piiResult = this.privacy.anonymize(sanitizedPrompt);
95
104
  sanitizedPrompt = piiResult.sanitizedValue || sanitizedPrompt;
96
- if (!piiResult.safe)
105
+ if (!piiResult.safe) {
97
106
  threats.push(...piiResult.threats);
107
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, 0.4); // PII is medium risk
108
+ }
98
109
  // 2. Prompt Injection (Firewall)
99
110
  // Only run if configured enabled (defaults true)
100
111
  const guardResult = this.guard.check(sanitizedPrompt);
101
112
  if (!guardResult.safe)
102
113
  threats.push(...guardResult.threats);
114
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, guardResult.riskScore || 0);
103
115
  // 3. DB Guard
104
116
  if (this.config.dbProtection.enabled) {
105
117
  const vaultResult = this.vault.checkSQL(sanitizedPrompt);
106
118
  if (!vaultResult.safe)
107
119
  threats.push(...vaultResult.threats);
120
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, vaultResult.riskScore || 0);
108
121
  }
109
- // 4. Resource Control (Rate limits check excluded for stateless call, but Token Check relevant)
122
+ // 4. Resource Control
110
123
  const tokenCheck = this.sentry.checkTokenCount(sanitizedPrompt);
111
- if (!tokenCheck.safe)
124
+ if (!tokenCheck.safe) {
112
125
  threats.push(...tokenCheck.threats);
126
+ cumulativeRiskScore = Math.max(cumulativeRiskScore, 0.2);
127
+ }
113
128
  return {
114
129
  output: sanitizedPrompt,
115
130
  threats,
116
131
  safe: threats.length === 0,
132
+ riskScore: cumulativeRiskScore,
117
133
  metadata: {
118
134
  estimatedTokens: tokenCheck.metadata?.estimatedTokens
119
135
  }
@@ -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
@@ -59,7 +59,8 @@ class Privacy {
59
59
  return {
60
60
  safe: threats.length === 0, // It is technically "safe" now that it is redacted, but we flag the threat presence
61
61
  threats,
62
- sanitizedValue
62
+ sanitizedValue,
63
+ riskScore: threats.length > 0 ? 0.6 : 0
63
64
  };
64
65
  }
65
66
  }
@@ -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,38 +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, only SELECT is usually allowed
22
+ // 2. Read-Only Enforcement (Moderate)
20
23
  if (this.config.mode === 'read-only') {
21
- const isSelect = upperQuery.trim().startsWith('SELECT');
22
- if (!isSelect && query.trim().length > 0) {
23
- threats.push("Non-SELECT query detected in read-only mode");
24
+ const firstWord = upperQuery.split(/\s+/)[0];
25
+ const sqlCommands = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "GRANT", "REVOKE", "TRUNCATE", "MERGE", "REPLACE", "UPSERT"];
26
+ if (sqlCommands.includes(firstWord)) {
27
+ threats.push(`Non-SELECT query detected in read-only mode (starts with ${firstWord})`);
28
+ riskScore += 0.8;
24
29
  }
25
30
  }
26
- // Check for common SQL injection markers
31
+ // 3. Injection Markers (High)
27
32
  const sqlInjectionMarkers = [
28
- /--/,
29
- /\/\*/,
30
- /;\s*DROP/i,
31
- /UNION\s+SELECT/i,
32
- /'\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 }
33
39
  ];
34
- for (const marker of sqlInjectionMarkers) {
35
- if (marker.test(query)) {
36
- 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;
37
44
  }
38
45
  }
46
+ riskScore = Math.min(1.0, riskScore);
39
47
  return {
40
48
  safe: threats.length === 0,
41
- threats
49
+ threats,
50
+ riskScore
42
51
  };
43
52
  }
44
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.4",
3
+ "version": "1.0.6",
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",
@@ -17,6 +17,7 @@
17
17
  "type": "git",
18
18
  "url": "git+https://github.com/himanshu-mamgain/onion-ai.git"
19
19
  },
20
+ "homepage": "https://himanshu-mamgain.github.io/onion-ai/",
20
21
  "keywords": [
21
22
  "ai",
22
23
  "security",