agent-security-scanner-mcp 3.3.0 → 3.4.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/src/utils.js CHANGED
@@ -36,10 +36,14 @@ export function detectLanguage(filePath) {
36
36
  }
37
37
 
38
38
  // Run the Python analyzer
39
- export function runAnalyzer(filePath) {
39
+ export function runAnalyzer(filePath, engine = 'auto') {
40
40
  try {
41
41
  const analyzerPath = join(__dirname, '..', 'analyzer.py');
42
- const result = execFileSync('python3', [analyzerPath, filePath], {
42
+ const args = [analyzerPath, filePath];
43
+ if (engine !== 'auto') {
44
+ args.push('--engine', engine);
45
+ }
46
+ const result = execFileSync('python3', args, {
43
47
  encoding: 'utf-8',
44
48
  timeout: 30000
45
49
  });
@@ -49,17 +53,62 @@ export function runAnalyzer(filePath) {
49
53
  }
50
54
  }
51
55
 
56
+ // Validate that a fix produces syntactically reasonable output
57
+ export function validateFix(original, fixed) {
58
+ if (!fixed || fixed === original) return false;
59
+
60
+ // Strip escaped quotes for bracket/quote counting
61
+ const unescaped = fixed.replace(/\\["'`]/g, '');
62
+
63
+ // Check balanced quotes (single pass)
64
+ const singleQ = (unescaped.match(/'/g) || []).length;
65
+ const doubleQ = (unescaped.match(/"/g) || []).length;
66
+ const backtickQ = (unescaped.match(/`/g) || []).length;
67
+ if (singleQ % 2 !== 0 || doubleQ % 2 !== 0 || backtickQ % 2 !== 0) return false;
68
+
69
+ // Check balanced brackets
70
+ const brackets = { '(': 0, '[': 0, '{': 0 };
71
+ const closers = { ')': '(', ']': '[', '}': '{' };
72
+ for (const char of unescaped) {
73
+ if (brackets[char] !== undefined) brackets[char]++;
74
+ if (closers[char]) {
75
+ brackets[closers[char]]--;
76
+ if (brackets[closers[char]] < 0) return false;
77
+ }
78
+ }
79
+ if (Object.values(brackets).some(v => v !== 0)) return false;
80
+
81
+ return true;
82
+ }
83
+
52
84
  // Generate fix suggestion for an issue
53
85
  export function generateFix(issue, line, language) {
54
86
  const ruleId = issue.ruleId.toLowerCase();
55
87
 
56
88
  for (const [pattern, template] of Object.entries(FIX_TEMPLATES)) {
57
89
  if (ruleId.includes(pattern)) {
58
- return {
59
- description: template.description,
60
- original: line,
61
- fixed: template.fix(line, language)
62
- };
90
+ try {
91
+ const fixed = template.fix(line, language);
92
+ // Validate the fix produces reasonable output
93
+ if (fixed && !validateFix(line, fixed)) {
94
+ return {
95
+ description: template.description + " (manual fix required)",
96
+ original: line,
97
+ fixed: null
98
+ };
99
+ }
100
+ return {
101
+ description: template.description,
102
+ original: line,
103
+ fixed: fixed
104
+ };
105
+ } catch {
106
+ return {
107
+ description: template.description + " (manual fix required)",
108
+ original: line,
109
+ fixed: null
110
+ };
111
+ }
63
112
  }
64
113
  }
65
114
 
@@ -70,6 +119,26 @@ export function generateFix(issue, line, language) {
70
119
  };
71
120
  }
72
121
 
122
+ // Run cross-file taint analysis
123
+ export function runCrossFileAnalyzer(filePaths) {
124
+ try {
125
+ const analyzerPath = join(__dirname, '..', 'cross_file_analyzer.py');
126
+ if (!existsSync(analyzerPath)) return [];
127
+ const result = execFileSync('python3', [analyzerPath, ...filePaths], {
128
+ encoding: 'utf-8',
129
+ timeout: 120000,
130
+ maxBuffer: 10 * 1024 * 1024
131
+ });
132
+ const parsed = JSON.parse(result);
133
+ // Return only cross-file warnings (per-file findings are handled by scanSecurity)
134
+ return Array.isArray(parsed)
135
+ ? parsed.filter(f => f.ruleId === 'cross-file-taint-warning')
136
+ : [];
137
+ } catch {
138
+ return [];
139
+ }
140
+ }
141
+
73
142
  // Convert issues to SARIF 2.1.0 format
74
143
  export function toSarif(file_path, language, issues) {
75
144
  const severityToLevel = {