sovr-mcp-proxy 7.0.0 → 7.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.
@@ -0,0 +1,701 @@
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/semanticAnalyzer.ts
21
+ var semanticAnalyzer_exports = {};
22
+ __export(semanticAnalyzer_exports, {
23
+ SemanticAnalyzer: () => SemanticAnalyzer,
24
+ createParanoidAnalyzer: () => createParanoidAnalyzer,
25
+ createSemanticAnalyzer: () => createSemanticAnalyzer
26
+ });
27
+ module.exports = __toCommonJS(semanticAnalyzer_exports);
28
+ var BUILTIN_RULES = [
29
+ // ── Data Destruction ──
30
+ {
31
+ id: "destroy-rm-rf",
32
+ name: "Recursive file deletion",
33
+ category: "data_destruction",
34
+ patterns: [
35
+ { type: "regex", value: "rm\\s+(-[a-zA-Z]*r[a-zA-Z]*f|--recursive)\\s", target: "command" },
36
+ { type: "regex", value: "rm\\s+(-[a-zA-Z]*f[a-zA-Z]*r)\\s", target: "command" },
37
+ { type: "keyword", value: "shred", target: "command" },
38
+ { type: "keyword", value: "wipe", target: "command" }
39
+ ],
40
+ riskLevel: "critical",
41
+ priority: 100,
42
+ polarity: "negative"
43
+ },
44
+ {
45
+ id: "destroy-db-drop",
46
+ name: "Database destruction",
47
+ category: "data_destruction",
48
+ patterns: [
49
+ { type: "regex", value: "DROP\\s+(TABLE|DATABASE|SCHEMA|INDEX)", target: "arguments", caseSensitive: false },
50
+ { type: "regex", value: "TRUNCATE\\s+TABLE", target: "arguments", caseSensitive: false },
51
+ { type: "regex", value: "DELETE\\s+FROM\\s+\\w+\\s*$", target: "arguments", caseSensitive: false }
52
+ // DELETE without WHERE
53
+ ],
54
+ riskLevel: "critical",
55
+ priority: 100,
56
+ polarity: "negative"
57
+ },
58
+ {
59
+ id: "destroy-format",
60
+ name: "Disk formatting",
61
+ category: "data_destruction",
62
+ patterns: [
63
+ { type: "regex", value: "mkfs\\.", target: "command" },
64
+ { type: "regex", value: "dd\\s+.*of=/dev/", target: "command" },
65
+ { type: "regex", value: "format\\s+[A-Z]:", target: "command", caseSensitive: false }
66
+ ],
67
+ riskLevel: "critical",
68
+ priority: 100,
69
+ polarity: "negative"
70
+ },
71
+ {
72
+ id: "destroy-find-delete",
73
+ name: "Find and delete (rm -rf equivalent)",
74
+ category: "data_destruction",
75
+ patterns: [
76
+ { type: "regex", value: "find\\s+.*-delete", target: "command" },
77
+ { type: "regex", value: "find\\s+.*-exec\\s+rm", target: "command" },
78
+ { type: "regex", value: "xargs\\s+rm", target: "command" }
79
+ ],
80
+ riskLevel: "dangerous",
81
+ priority: 95,
82
+ polarity: "negative"
83
+ },
84
+ // ── Data Exfiltration ──
85
+ {
86
+ id: "exfil-curl-post",
87
+ name: "Data upload via curl/wget",
88
+ category: "data_exfiltration",
89
+ patterns: [
90
+ { type: "regex", value: "curl\\s+.*(-d|--data|--upload-file|-F|--form)\\s", target: "command" },
91
+ { type: "regex", value: "curl\\s+.*-X\\s*(POST|PUT)", target: "command", caseSensitive: false },
92
+ { type: "regex", value: "wget\\s+.*--post", target: "command" }
93
+ ],
94
+ riskLevel: "suspicious",
95
+ priority: 80,
96
+ polarity: "negative"
97
+ },
98
+ {
99
+ id: "exfil-pipe-network",
100
+ name: "Piping data to network",
101
+ category: "data_exfiltration",
102
+ patterns: [
103
+ { type: "regex", value: "cat\\s+.*\\|\\s*(nc|netcat|curl|wget)", target: "command" },
104
+ { type: "regex", value: "(tar|zip|gzip)\\s+.*\\|\\s*(nc|curl)", target: "command" },
105
+ { type: "regex", value: "base64\\s+.*\\|\\s*curl", target: "command" }
106
+ ],
107
+ riskLevel: "dangerous",
108
+ priority: 90,
109
+ polarity: "negative"
110
+ },
111
+ {
112
+ id: "exfil-dns",
113
+ name: "DNS exfiltration",
114
+ category: "data_exfiltration",
115
+ patterns: [
116
+ { type: "regex", value: "dig\\s+.*\\$\\(", target: "command" },
117
+ { type: "regex", value: "nslookup\\s+.*\\$\\(", target: "command" }
118
+ ],
119
+ riskLevel: "critical",
120
+ priority: 95,
121
+ polarity: "negative"
122
+ },
123
+ // ── Privilege Escalation ──
124
+ {
125
+ id: "privesc-sudo",
126
+ name: "Privilege escalation via sudo",
127
+ category: "privilege_escalation",
128
+ patterns: [
129
+ { type: "regex", value: "sudo\\s+", target: "command" },
130
+ { type: "regex", value: "su\\s+-", target: "command" },
131
+ { type: "keyword", value: "doas", target: "command" }
132
+ ],
133
+ riskLevel: "dangerous",
134
+ priority: 85,
135
+ polarity: "negative"
136
+ },
137
+ {
138
+ id: "privesc-chmod",
139
+ name: "Permission modification",
140
+ category: "privilege_escalation",
141
+ patterns: [
142
+ { type: "regex", value: "chmod\\s+(777|\\+s|u\\+s|g\\+s)", target: "command" },
143
+ { type: "regex", value: "chown\\s+root", target: "command" },
144
+ { type: "regex", value: "setuid", target: "command" }
145
+ ],
146
+ riskLevel: "dangerous",
147
+ priority: 85,
148
+ polarity: "negative"
149
+ },
150
+ // ── Code Execution ──
151
+ {
152
+ id: "exec-remote",
153
+ name: "Remote code execution",
154
+ category: "code_execution",
155
+ patterns: [
156
+ { type: "regex", value: "curl\\s+.*\\|\\s*(sh|bash|python|node|perl|ruby)", target: "command" },
157
+ { type: "regex", value: "wget\\s+.*\\|\\s*(sh|bash)", target: "command" },
158
+ { type: "regex", value: "eval\\s*\\(", target: "arguments" },
159
+ { type: "regex", value: "exec\\s*\\(", target: "arguments" }
160
+ ],
161
+ riskLevel: "critical",
162
+ priority: 100,
163
+ polarity: "negative"
164
+ },
165
+ {
166
+ id: "exec-obfuscated",
167
+ name: "Obfuscated execution",
168
+ category: "code_execution",
169
+ patterns: [
170
+ { type: "regex", value: "\\$\\(.*\\)", target: "command" },
171
+ { type: "regex", value: "`[^`]+`", target: "command" },
172
+ { type: "regex", value: "base64\\s+(-d|--decode)", target: "command" },
173
+ { type: "regex", value: "\\\\x[0-9a-fA-F]{2}", target: "arguments" }
174
+ ],
175
+ riskLevel: "suspicious",
176
+ priority: 75,
177
+ polarity: "negative"
178
+ },
179
+ // ── Credential Access ──
180
+ {
181
+ id: "cred-env",
182
+ name: "Environment variable access",
183
+ category: "credential_access",
184
+ patterns: [
185
+ { type: "regex", value: "\\$\\{?(API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)", target: "command", caseSensitive: false },
186
+ { type: "regex", value: "printenv|env\\s*$", target: "command" },
187
+ { type: "regex", value: "cat\\s+.*\\.(env|secret|key|pem|crt)", target: "command" }
188
+ ],
189
+ riskLevel: "dangerous",
190
+ priority: 90,
191
+ polarity: "negative"
192
+ },
193
+ {
194
+ id: "cred-ssh",
195
+ name: "SSH key access",
196
+ category: "credential_access",
197
+ patterns: [
198
+ { type: "regex", value: "cat\\s+.*\\.ssh/(id_rsa|id_ed25519|authorized_keys)", target: "command" },
199
+ { type: "regex", value: "ssh-keygen", target: "command" }
200
+ ],
201
+ riskLevel: "dangerous",
202
+ priority: 85,
203
+ polarity: "negative"
204
+ },
205
+ // ── Financial Operations ──
206
+ {
207
+ id: "finance-payment",
208
+ name: "Payment/transfer operation",
209
+ category: "financial_operation",
210
+ patterns: [
211
+ { type: "keyword", value: "payment", target: "tool_name" },
212
+ { type: "keyword", value: "transfer", target: "tool_name" },
213
+ { type: "keyword", value: "checkout", target: "tool_name" },
214
+ { type: "keyword", value: "invoice", target: "tool_name" },
215
+ { type: "regex", value: "stripe|paypal|braintree", target: "arguments", caseSensitive: false }
216
+ ],
217
+ riskLevel: "dangerous",
218
+ priority: 90,
219
+ polarity: "negative"
220
+ },
221
+ // ── System Modification ──
222
+ {
223
+ id: "sysmod-service",
224
+ name: "System service modification",
225
+ category: "system_modification",
226
+ patterns: [
227
+ { type: "regex", value: "systemctl\\s+(stop|disable|mask|restart)", target: "command" },
228
+ { type: "regex", value: "service\\s+\\w+\\s+(stop|restart)", target: "command" },
229
+ { type: "regex", value: "kill\\s+-9", target: "command" },
230
+ { type: "regex", value: "pkill", target: "command" }
231
+ ],
232
+ riskLevel: "suspicious",
233
+ priority: 70,
234
+ polarity: "negative"
235
+ },
236
+ {
237
+ id: "sysmod-cron",
238
+ name: "Cron job modification",
239
+ category: "system_modification",
240
+ patterns: [
241
+ { type: "regex", value: "crontab\\s+-[er]", target: "command" },
242
+ { type: "regex", value: "/etc/cron", target: "command" }
243
+ ],
244
+ riskLevel: "dangerous",
245
+ priority: 80,
246
+ polarity: "negative"
247
+ },
248
+ // ── Read-Only (Positive) ──
249
+ {
250
+ id: "readonly-safe",
251
+ name: "Safe read-only commands",
252
+ category: "read_only",
253
+ patterns: [
254
+ { type: "regex", value: "^(ls|cat|head|tail|grep|find|echo|pwd|whoami|date|wc|file|stat|du|df|uptime|hostname)\\b", target: "command" },
255
+ { type: "regex", value: "^SELECT\\s", target: "arguments", caseSensitive: false }
256
+ ],
257
+ riskLevel: "safe",
258
+ priority: 50,
259
+ polarity: "positive"
260
+ },
261
+ {
262
+ id: "readonly-git",
263
+ name: "Safe git read operations",
264
+ category: "read_only",
265
+ patterns: [
266
+ { type: "regex", value: "git\\s+(status|log|diff|show|branch|remote|tag)\\b", target: "command" }
267
+ ],
268
+ riskLevel: "safe",
269
+ priority: 50,
270
+ polarity: "positive"
271
+ }
272
+ ];
273
+ function analyzeStructure(command) {
274
+ const structure = {
275
+ baseCommand: "",
276
+ args: [],
277
+ pipeChain: [],
278
+ redirections: [],
279
+ subshells: [],
280
+ envVars: [],
281
+ filePaths: [],
282
+ networkTargets: [],
283
+ isWrapped: false,
284
+ complexity: 0
285
+ };
286
+ const wrapMatch = command.match(/^(bash|sh|zsh|dash|ksh)\s+(-c\s+)?["'](.+)["']$/);
287
+ if (wrapMatch) {
288
+ structure.isWrapped = true;
289
+ structure.complexity += 20;
290
+ const inner = analyzeStructure(wrapMatch[3]);
291
+ return { ...inner, isWrapped: true, complexity: inner.complexity + 20 };
292
+ }
293
+ structure.pipeChain = command.split(/\s*\|\s*/).map((s) => s.trim()).filter(Boolean);
294
+ structure.complexity += (structure.pipeChain.length - 1) * 10;
295
+ const firstCmd = structure.pipeChain[0] || command;
296
+ const parts = firstCmd.split(/\s+/);
297
+ structure.baseCommand = parts[0] || "";
298
+ structure.args = parts.slice(1);
299
+ const redirectMatches = command.match(/[12]?>>?\s*\S+/g);
300
+ if (redirectMatches) {
301
+ structure.redirections = redirectMatches;
302
+ structure.complexity += redirectMatches.length * 5;
303
+ }
304
+ const subshellMatches = command.match(/\$\([^)]+\)/g);
305
+ if (subshellMatches) {
306
+ structure.subshells = subshellMatches;
307
+ structure.complexity += subshellMatches.length * 15;
308
+ }
309
+ const backtickMatches = command.match(/`[^`]+`/g);
310
+ if (backtickMatches) {
311
+ structure.subshells.push(...backtickMatches);
312
+ structure.complexity += backtickMatches.length * 15;
313
+ }
314
+ const envMatches = command.match(/\$\{?\w+\}?/g);
315
+ if (envMatches) {
316
+ structure.envVars = envMatches;
317
+ structure.complexity += envMatches.length * 3;
318
+ }
319
+ const pathMatches = command.match(/(?:\/[\w.-]+)+/g);
320
+ if (pathMatches) {
321
+ structure.filePaths = pathMatches;
322
+ }
323
+ const urlMatches = command.match(/https?:\/\/[^\s'"]+/g);
324
+ if (urlMatches) {
325
+ structure.networkTargets = urlMatches;
326
+ structure.complexity += urlMatches.length * 5;
327
+ }
328
+ const ipMatches = command.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g);
329
+ if (ipMatches) {
330
+ structure.networkTargets.push(...ipMatches);
331
+ structure.complexity += ipMatches.length * 5;
332
+ }
333
+ return structure;
334
+ }
335
+ function structuralRiskAssessment(structure) {
336
+ const findings = [];
337
+ let riskScore = 0;
338
+ if (structure.isWrapped) {
339
+ findings.push("Command is wrapped in bash -c (possible obfuscation)");
340
+ riskScore += 20;
341
+ }
342
+ if (structure.pipeChain.length > 3) {
343
+ findings.push(`Complex pipe chain (${structure.pipeChain.length} stages)`);
344
+ riskScore += 15;
345
+ }
346
+ if (structure.subshells.length > 0) {
347
+ findings.push(`Command substitution detected (${structure.subshells.length} instances)`);
348
+ riskScore += structure.subshells.length * 10;
349
+ }
350
+ const sensitivePaths = structure.filePaths.filter(
351
+ (p) => /\/(etc|root|\.ssh|\.gnupg|\.aws|\.config|proc|sys|dev)\//.test(p)
352
+ );
353
+ if (sensitivePaths.length > 0) {
354
+ findings.push(`Sensitive paths accessed: ${sensitivePaths.join(", ")}`);
355
+ riskScore += sensitivePaths.length * 15;
356
+ }
357
+ if (structure.networkTargets.length > 0) {
358
+ findings.push(`Network targets: ${structure.networkTargets.join(", ")}`);
359
+ riskScore += 10;
360
+ }
361
+ if (structure.complexity > 50) {
362
+ findings.push(`High command complexity (score: ${structure.complexity})`);
363
+ riskScore += 10;
364
+ }
365
+ if (structure.redirections.length > 1) {
366
+ findings.push(`Multiple redirections (${structure.redirections.length})`);
367
+ riskScore += 5;
368
+ }
369
+ riskScore = Math.min(riskScore, 100);
370
+ let riskLevel;
371
+ if (riskScore < 20) riskLevel = "safe";
372
+ else if (riskScore < 45) riskLevel = "suspicious";
373
+ else if (riskScore < 70) riskLevel = "dangerous";
374
+ else riskLevel = "critical";
375
+ return {
376
+ riskLevel,
377
+ riskScore,
378
+ confidence: 0.7,
379
+ // Structural analysis has moderate confidence
380
+ findings
381
+ };
382
+ }
383
+ function buildLLMPrompt(toolName, args, context) {
384
+ return `You are SOVR Security Analyzer. Analyze this AI agent tool call for security risks.
385
+
386
+ TOOL: ${toolName}
387
+ ARGUMENTS: ${JSON.stringify(args, null, 2)}
388
+
389
+ PRIOR ANALYSIS:
390
+ - Rule-based risk: ${context.ruleResult.riskLevel} (score: ${context.ruleResult.riskScore})
391
+ Findings: ${context.ruleResult.findings.join("; ") || "none"}
392
+ - Structural risk: ${context.structuralResult.riskLevel} (score: ${context.structuralResult.riskScore})
393
+ Findings: ${context.structuralResult.findings.join("; ") || "none"}
394
+
395
+ Respond in JSON format ONLY:
396
+ {
397
+ "intent": "brief description of what this command intends to do",
398
+ "riskLevel": "safe|suspicious|dangerous|critical",
399
+ "confidence": 0.0-1.0,
400
+ "reasoning": "explain your assessment"
401
+ }
402
+
403
+ Rules:
404
+ - Focus on INTENT, not just syntax
405
+ - Consider if the command could be a disguised destructive operation
406
+ - "safe" = read-only or benign modification
407
+ - "suspicious" = could be harmful but has legitimate uses
408
+ - "dangerous" = likely harmful, should require approval
409
+ - "critical" = almost certainly destructive or exfiltrating`;
410
+ }
411
+ var SemanticAnalyzer = class {
412
+ config;
413
+ rules;
414
+ constructor(config = {}) {
415
+ this.config = {
416
+ enableLLM: config.enableLLM ?? false,
417
+ llmProvider: config.llmProvider,
418
+ llmTriggerThreshold: config.llmTriggerThreshold ?? 40,
419
+ llmTimeout: config.llmTimeout ?? 1e4,
420
+ customRules: config.customRules ?? [],
421
+ enableStructural: config.enableStructural ?? true,
422
+ sensitivity: config.sensitivity ?? "medium"
423
+ };
424
+ this.rules = [...BUILTIN_RULES, ...this.config.customRules].sort((a, b) => b.priority - a.priority);
425
+ }
426
+ /**
427
+ * Analyze a tool call for security risks.
428
+ * Returns a comprehensive analysis result with multi-layer findings.
429
+ */
430
+ async analyze(toolName, args) {
431
+ const startTime = Date.now();
432
+ const ruleResult = this.analyzeWithRules(toolName, args);
433
+ let structuralResult = { riskLevel: "safe", riskScore: 0, confidence: 0, findings: [] };
434
+ if (this.config.enableStructural) {
435
+ const command = this.extractCommand(toolName, args);
436
+ if (command) {
437
+ const structure = analyzeStructure(command);
438
+ structuralResult = structuralRiskAssessment(structure);
439
+ }
440
+ }
441
+ let llmResult;
442
+ const combinedScore = Math.max(ruleResult.riskScore, structuralResult.riskScore);
443
+ if (this.config.enableLLM && this.config.llmProvider && combinedScore >= this.config.llmTriggerThreshold && combinedScore < 80) {
444
+ try {
445
+ const prompt = buildLLMPrompt(toolName, args, { ruleResult, structuralResult });
446
+ const judgment = await this.config.llmProvider.analyze(prompt, this.config.llmTimeout);
447
+ llmResult = {
448
+ riskLevel: judgment.riskLevel,
449
+ riskScore: this.riskLevelToScore(judgment.riskLevel),
450
+ confidence: judgment.confidence,
451
+ findings: [`LLM intent: ${judgment.intent}`, `LLM reasoning: ${judgment.reasoning}`]
452
+ };
453
+ } catch {
454
+ llmResult = void 0;
455
+ }
456
+ }
457
+ const intents = this.collectIntents(ruleResult, structuralResult, llmResult);
458
+ const finalResult = this.combineResults(ruleResult, structuralResult, llmResult);
459
+ return {
460
+ ...finalResult,
461
+ intents,
462
+ layers: {
463
+ rules: ruleResult,
464
+ structural: structuralResult,
465
+ llm: llmResult
466
+ },
467
+ durationMs: Date.now() - startTime
468
+ };
469
+ }
470
+ /** Quick synchronous check (Layer 1 only, for hot path) */
471
+ quickCheck(toolName, args) {
472
+ const result = this.analyzeWithRules(toolName, args);
473
+ return {
474
+ riskLevel: result.riskLevel,
475
+ riskScore: result.riskScore,
476
+ topFinding: result.findings[0] || "No findings"
477
+ };
478
+ }
479
+ /** Add custom rules at runtime */
480
+ addRule(rule) {
481
+ this.rules.push(rule);
482
+ this.rules.sort((a, b) => b.priority - a.priority);
483
+ }
484
+ /** Get all active rules */
485
+ getRules() {
486
+ return [...this.rules];
487
+ }
488
+ // ─── Private ─────────────────────────────────────────────────────────────
489
+ analyzeWithRules(toolName, args) {
490
+ const findings = [];
491
+ let maxRiskScore = 0;
492
+ let maxRiskLevel = "safe";
493
+ let hasPositiveMatch = false;
494
+ const argsStr = JSON.stringify(args);
495
+ const command = this.extractCommand(toolName, args);
496
+ for (const rule of this.rules) {
497
+ let matched = false;
498
+ for (const pattern of rule.patterns) {
499
+ const target = this.getPatternTarget(pattern.target, toolName, command, argsStr);
500
+ if (!target) continue;
501
+ switch (pattern.type) {
502
+ case "regex": {
503
+ const flags = pattern.caseSensitive === false ? "i" : "";
504
+ const regex = new RegExp(pattern.value, flags);
505
+ if (regex.test(target)) matched = true;
506
+ break;
507
+ }
508
+ case "keyword": {
509
+ const searchIn = pattern.caseSensitive ? target : target.toLowerCase();
510
+ const searchFor = pattern.caseSensitive ? pattern.value : pattern.value.toLowerCase();
511
+ if (searchIn.includes(searchFor)) matched = true;
512
+ break;
513
+ }
514
+ case "sequence": {
515
+ const keywords = pattern.value.split(",").map((k) => k.trim());
516
+ let lastIndex = -1;
517
+ let allFound = true;
518
+ for (const kw of keywords) {
519
+ const idx = target.indexOf(kw, lastIndex + 1);
520
+ if (idx === -1) {
521
+ allFound = false;
522
+ break;
523
+ }
524
+ lastIndex = idx;
525
+ }
526
+ if (allFound) matched = true;
527
+ break;
528
+ }
529
+ }
530
+ if (matched) break;
531
+ }
532
+ if (matched) {
533
+ if (rule.polarity === "positive") {
534
+ hasPositiveMatch = true;
535
+ findings.push(`\u2713 ${rule.name}`);
536
+ } else {
537
+ findings.push(`\u2717 ${rule.name} [${rule.riskLevel}]`);
538
+ const ruleScore = this.riskLevelToScore(rule.riskLevel);
539
+ if (ruleScore > maxRiskScore) {
540
+ maxRiskScore = ruleScore;
541
+ maxRiskLevel = rule.riskLevel;
542
+ }
543
+ }
544
+ }
545
+ }
546
+ maxRiskScore = this.applySensitivity(maxRiskScore);
547
+ if (hasPositiveMatch && maxRiskScore === 0) {
548
+ return { riskLevel: "safe", riskScore: 0, confidence: 0.9, findings };
549
+ }
550
+ return {
551
+ riskLevel: maxRiskLevel,
552
+ riskScore: maxRiskScore,
553
+ confidence: findings.length > 0 ? 0.85 : 0.5,
554
+ findings
555
+ };
556
+ }
557
+ extractCommand(toolName, args) {
558
+ for (const key of ["command", "cmd", "query", "sql", "script", "code", "input"]) {
559
+ if (typeof args[key] === "string") return args[key];
560
+ }
561
+ if (["bash", "shell", "exec", "run_command"].includes(toolName.toLowerCase())) {
562
+ const firstStr = Object.values(args).find((v) => typeof v === "string");
563
+ if (firstStr) return firstStr;
564
+ }
565
+ return null;
566
+ }
567
+ getPatternTarget(target, toolName, command, argsStr) {
568
+ switch (target) {
569
+ case "tool_name":
570
+ return toolName;
571
+ case "command":
572
+ return command;
573
+ case "arguments":
574
+ return argsStr;
575
+ case "full_context":
576
+ return `${toolName} ${command || ""} ${argsStr}`;
577
+ default:
578
+ return null;
579
+ }
580
+ }
581
+ riskLevelToScore(level) {
582
+ switch (level) {
583
+ case "safe":
584
+ return 0;
585
+ case "suspicious":
586
+ return 35;
587
+ case "dangerous":
588
+ return 65;
589
+ case "critical":
590
+ return 90;
591
+ default:
592
+ return 50;
593
+ }
594
+ }
595
+ applySensitivity(score) {
596
+ switch (this.config.sensitivity) {
597
+ case "low":
598
+ return Math.max(0, score - 15);
599
+ case "medium":
600
+ return score;
601
+ case "high":
602
+ return Math.min(100, score + 10);
603
+ case "paranoid":
604
+ return Math.min(100, score + 25);
605
+ }
606
+ }
607
+ collectIntents(ruleResult, structuralResult, llmResult) {
608
+ const intents = [];
609
+ for (const finding of ruleResult.findings) {
610
+ if (finding.startsWith("\u2717")) {
611
+ const match = finding.match(/✗ (.+) \[(\w+)\]/);
612
+ if (match) {
613
+ intents.push({
614
+ category: "code_execution",
615
+ // Simplified — real impl would map from rule
616
+ description: match[1],
617
+ confidence: ruleResult.confidence,
618
+ source: "rule",
619
+ evidence: [finding]
620
+ });
621
+ }
622
+ }
623
+ }
624
+ for (const finding of structuralResult.findings) {
625
+ intents.push({
626
+ category: "code_execution",
627
+ description: finding,
628
+ confidence: structuralResult.confidence,
629
+ source: "structural",
630
+ evidence: [finding]
631
+ });
632
+ }
633
+ if (llmResult) {
634
+ for (const finding of llmResult.findings) {
635
+ intents.push({
636
+ category: "code_execution",
637
+ description: finding,
638
+ confidence: llmResult.confidence,
639
+ source: "llm",
640
+ evidence: [finding]
641
+ });
642
+ }
643
+ }
644
+ return intents;
645
+ }
646
+ combineResults(ruleResult, structuralResult, llmResult) {
647
+ const ruleWeight = 0.5;
648
+ const structWeight = 0.3;
649
+ const llmWeight = llmResult ? 0.2 : 0;
650
+ const normalizer = ruleWeight + structWeight + llmWeight;
651
+ const combinedScore = Math.round(
652
+ (ruleResult.riskScore * ruleWeight + structuralResult.riskScore * structWeight + (llmResult?.riskScore ?? 0) * llmWeight) / normalizer
653
+ );
654
+ const levels = [ruleResult.riskLevel, structuralResult.riskLevel, llmResult?.riskLevel].filter(Boolean);
655
+ const levelOrder = { safe: 0, suspicious: 1, dangerous: 2, critical: 3 };
656
+ const maxLevel = levels.reduce(
657
+ (max, l) => (levelOrder[l] ?? 0) > (levelOrder[max] ?? 0) ? l : max,
658
+ "safe"
659
+ );
660
+ const confidence = Math.round(
661
+ (ruleResult.confidence * ruleWeight + structuralResult.confidence * structWeight + (llmResult?.confidence ?? 0) * llmWeight) / normalizer * 100
662
+ ) / 100;
663
+ let recommendation;
664
+ if (combinedScore < 20) recommendation = "allow";
665
+ else if (combinedScore < 45) recommendation = "warn";
666
+ else if (combinedScore < 70) recommendation = "require-approval";
667
+ else recommendation = "block";
668
+ const allFindings = [
669
+ ...ruleResult.findings,
670
+ ...structuralResult.findings,
671
+ ...llmResult?.findings ?? []
672
+ ];
673
+ const explanation = allFindings.length > 0 ? `Risk: ${maxLevel} (score: ${combinedScore}). Findings: ${allFindings.slice(0, 3).join("; ")}` : `Risk: ${maxLevel} (score: ${combinedScore}). No specific findings.`;
674
+ return {
675
+ riskLevel: maxLevel,
676
+ riskScore: combinedScore,
677
+ confidence,
678
+ recommendation,
679
+ explanation
680
+ };
681
+ }
682
+ };
683
+ function createSemanticAnalyzer(overrides = {}) {
684
+ return new SemanticAnalyzer(overrides);
685
+ }
686
+ function createParanoidAnalyzer(llmProvider) {
687
+ return new SemanticAnalyzer({
688
+ enableLLM: !!llmProvider,
689
+ llmProvider,
690
+ llmTriggerThreshold: 20,
691
+ llmTimeout: 15e3,
692
+ enableStructural: true,
693
+ sensitivity: "paranoid"
694
+ });
695
+ }
696
+ // Annotate the CommonJS export names for ESM import in node:
697
+ 0 && (module.exports = {
698
+ SemanticAnalyzer,
699
+ createParanoidAnalyzer,
700
+ createSemanticAnalyzer
701
+ });