agent-security-scanner-mcp 3.4.0 → 3.5.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.
@@ -2,15 +2,11 @@
2
2
  import { z } from "zod";
3
3
  import { existsSync, readFileSync } from "fs";
4
4
  import { detectLanguage, runAnalyzer, generateFix, toSarif } from '../utils.js';
5
- import { deduplicateFindings } from '../dedup.js';
6
- import { applyContextFilter, detectFrameworks, applyFrameworkAdjustments } from '../context.js';
7
- import { loadConfig, shouldExcludeFile, applyConfig } from '../config.js';
8
5
 
9
6
  export const scanSecuritySchema = {
10
7
  file_path: z.string().describe("Path to the file to scan"),
11
8
  output_format: z.enum(['json', 'sarif']).optional().describe("Output format: 'json' (default) or 'sarif' for GitHub/GitLab integration"),
12
- verbosity: z.enum(['minimal', 'compact', 'full']).optional().describe("Response detail level: 'minimal' (counts only), 'compact' (default, actionable info), 'full' (complete metadata)"),
13
- engine: z.enum(['auto', 'ast', 'regex']).optional().describe("Analysis engine: 'auto' (default, AST with regex fallback), 'ast' (tree-sitter only), 'regex' (regex only)")
9
+ verbosity: z.enum(['minimal', 'compact', 'full']).optional().describe("Response detail level: 'minimal' (counts only), 'compact' (default, actionable info), 'full' (complete metadata)")
14
10
  };
15
11
 
16
12
  // Verbosity formatters
@@ -39,7 +35,6 @@ function formatCompact(file_path, language, issues) {
39
35
  line: i.line + 1,
40
36
  ruleId: i.ruleId,
41
37
  severity: i.severity,
42
- confidence: i.confidence || 'MEDIUM',
43
38
  message: i.message,
44
39
  fix: i.suggested_fix?.fixed ? i.suggested_fix.fixed.trim() : null
45
40
  }))
@@ -55,49 +50,26 @@ function formatFull(file_path, language, issues) {
55
50
  };
56
51
  }
57
52
 
58
- export async function scanSecurity({ file_path, output_format, verbosity, engine }) {
53
+ export async function scanSecurity({ file_path, output_format, verbosity }) {
59
54
  if (!existsSync(file_path)) {
60
55
  return {
61
56
  content: [{ type: "text", text: JSON.stringify({ error: "File not found" }) }]
62
57
  };
63
58
  }
64
59
 
65
- // Load project configuration
66
- const config = loadConfig(file_path);
60
+ const issues = runAnalyzer(file_path);
67
61
 
68
- // Check file exclusion
69
- if (shouldExcludeFile(file_path, config)) {
62
+ if (issues.error) {
70
63
  return {
71
- content: [{ type: "text", text: JSON.stringify({ file: file_path, message: "File excluded by configuration", issues_count: 0 }) }]
64
+ content: [{ type: "text", text: JSON.stringify(issues) }]
72
65
  };
73
66
  }
74
67
 
75
- const rawIssues = runAnalyzer(file_path, engine || 'auto');
76
-
77
- if (rawIssues.error) {
78
- return {
79
- content: [{ type: "text", text: JSON.stringify(rawIssues) }]
80
- };
81
- }
82
-
83
- // Cross-engine deduplication
84
- const dedupedIssues = deduplicateFindings(rawIssues);
85
-
86
68
  // Read file content for fix suggestions
87
69
  const content = readFileSync(file_path, 'utf-8');
88
70
  const lines = content.split('\n');
89
71
  const language = detectLanguage(file_path);
90
72
 
91
- // Context-aware filtering (suppress known module imports)
92
- const contextFiltered = applyContextFilter(dedupedIssues, file_path, language);
93
-
94
- // Framework-aware severity adjustment
95
- const frameworks = detectFrameworks(file_path, language);
96
- const frameworkAdjusted = applyFrameworkAdjustments(contextFiltered, frameworks);
97
-
98
- // Apply .scannerrc configuration (rule suppression, severity/confidence thresholds)
99
- const issues = applyConfig(frameworkAdjusted, file_path, config);
100
-
101
73
  // Enhance issues with fix suggestions
102
74
  const enhancedIssues = issues.map(issue => {
103
75
  const line = lines[issue.line] || '';
package/src/utils.js CHANGED
@@ -36,14 +36,10 @@ export function detectLanguage(filePath) {
36
36
  }
37
37
 
38
38
  // Run the Python analyzer
39
- export function runAnalyzer(filePath, engine = 'auto') {
39
+ export function runAnalyzer(filePath) {
40
40
  try {
41
41
  const analyzerPath = join(__dirname, '..', 'analyzer.py');
42
- const args = [analyzerPath, filePath];
43
- if (engine !== 'auto') {
44
- args.push('--engine', engine);
45
- }
46
- const result = execFileSync('python3', args, {
42
+ const result = execFileSync('python3', [analyzerPath, filePath], {
47
43
  encoding: 'utf-8',
48
44
  timeout: 30000
49
45
  });
@@ -53,62 +49,17 @@ export function runAnalyzer(filePath, engine = 'auto') {
53
49
  }
54
50
  }
55
51
 
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
-
84
52
  // Generate fix suggestion for an issue
85
53
  export function generateFix(issue, line, language) {
86
54
  const ruleId = issue.ruleId.toLowerCase();
87
55
 
88
56
  for (const [pattern, template] of Object.entries(FIX_TEMPLATES)) {
89
57
  if (ruleId.includes(pattern)) {
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
- }
58
+ return {
59
+ description: template.description,
60
+ original: line,
61
+ fixed: template.fix(line, language)
62
+ };
112
63
  }
113
64
  }
114
65
 
@@ -119,26 +70,6 @@ export function generateFix(issue, line, language) {
119
70
  };
120
71
  }
121
72
 
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
-
142
73
  // Convert issues to SARIF 2.1.0 format
143
74
  export function toSarif(file_path, language, issues) {
144
75
  const severityToLevel = {
@@ -1,216 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Cross-file taint analysis for security scanning.
3
-
4
- Builds an import graph across local files, runs per-file analysis,
5
- and propagates taint warnings when a file imports from another file
6
- that has ERROR-severity findings.
7
- """
8
-
9
- import json
10
- import os
11
- import re
12
- import sys
13
-
14
- # Import the per-file analyzer
15
- from analyzer import analyze_file
16
-
17
-
18
- def extract_js_imports(source):
19
- """Extract import/require statements from JavaScript/TypeScript."""
20
- imports = []
21
- # require('...')
22
- for m in re.finditer(r'''require\s*\(\s*['"]([^'"]+)['"]\s*\)''', source):
23
- imports.append(m.group(1))
24
- # import ... from '...'
25
- for m in re.finditer(r'''from\s+['"]([^'"]+)['"]''', source):
26
- imports.append(m.group(1))
27
- # import '...'
28
- for m in re.finditer(r'''import\s+['"]([^'"]+)['"]''', source):
29
- imports.append(m.group(1))
30
- return imports
31
-
32
-
33
- def extract_py_imports(source):
34
- """Extract import statements from Python."""
35
- imports = []
36
- # import module
37
- for m in re.finditer(r'^import\s+(\S+)', source, re.MULTILINE):
38
- imports.append(m.group(1).split('.')[0])
39
- # from module import ...
40
- for m in re.finditer(r'^from\s+(\S+)\s+import', source, re.MULTILINE):
41
- imports.append(m.group(1).split('.')[0])
42
- return imports
43
-
44
-
45
- def detect_language(file_path):
46
- """Detect language from file extension."""
47
- ext = os.path.splitext(file_path)[1].lower()
48
- lang_map = {
49
- '.py': 'python', '.js': 'javascript', '.ts': 'typescript',
50
- '.tsx': 'typescript', '.jsx': 'javascript',
51
- }
52
- return lang_map.get(ext, 'unknown')
53
-
54
-
55
- def resolve_local_import(module, base_dir, lang):
56
- """Resolve a relative/local import to an actual file path."""
57
- if lang in ('javascript', 'typescript'):
58
- # Only resolve relative imports
59
- if not module.startswith('.'):
60
- return None
61
- # Try common extensions
62
- candidates = [
63
- module,
64
- module + '.js', module + '.ts', module + '.tsx', module + '.jsx',
65
- os.path.join(module, 'index.js'), os.path.join(module, 'index.ts'),
66
- ]
67
- for candidate in candidates:
68
- full = os.path.normpath(os.path.join(base_dir, candidate))
69
- if os.path.isfile(full):
70
- return full
71
- elif lang == 'python':
72
- # Only resolve relative imports (starting with .)
73
- if module.startswith('.'):
74
- rel = module.lstrip('.')
75
- candidates = [
76
- os.path.join(base_dir, rel.replace('.', os.sep) + '.py'),
77
- os.path.join(base_dir, rel.replace('.', os.sep), '__init__.py'),
78
- ]
79
- for candidate in candidates:
80
- if os.path.isfile(candidate):
81
- return candidate
82
- # Also check if the module name matches a sibling file
83
- sibling = os.path.join(base_dir, module + '.py')
84
- if os.path.isfile(sibling):
85
- return sibling
86
- return None
87
-
88
-
89
- def extract_exports(source, lang):
90
- """Extract exported function/class names."""
91
- exports = []
92
- if lang in ('javascript', 'typescript'):
93
- for m in re.finditer(r'export\s+(?:function|class|const|let|var)\s+(\w+)', source):
94
- exports.append(m.group(1))
95
- for m in re.finditer(r'module\.exports\s*=', source):
96
- exports.append('default')
97
- elif lang == 'python':
98
- for m in re.finditer(r'^(?:def|class)\s+(\w+)', source, re.MULTILINE):
99
- exports.append(m.group(1))
100
- return exports
101
-
102
-
103
- def build_import_graph(file_paths):
104
- """Build import graph: {file -> [{module, resolved_path, line}]}."""
105
- graph = {}
106
- file_set = set(os.path.abspath(f) for f in file_paths)
107
-
108
- for file_path in file_paths:
109
- abs_path = os.path.abspath(file_path)
110
- lang = detect_language(file_path)
111
- if lang == 'unknown':
112
- continue
113
-
114
- try:
115
- source = open(file_path, 'r', encoding='utf-8', errors='ignore').read()
116
- except (OSError, IOError):
117
- continue
118
-
119
- if lang in ('javascript', 'typescript'):
120
- modules = extract_js_imports(source)
121
- elif lang == 'python':
122
- modules = extract_py_imports(source)
123
- else:
124
- continue
125
-
126
- base_dir = os.path.dirname(abs_path)
127
- edges = []
128
- for mod in modules:
129
- resolved = resolve_local_import(mod, base_dir, lang)
130
- if resolved:
131
- resolved_abs = os.path.abspath(resolved)
132
- if resolved_abs in file_set and resolved_abs != abs_path:
133
- edges.append({
134
- 'module': mod,
135
- 'resolved_path': resolved_abs,
136
- })
137
-
138
- graph[abs_path] = edges
139
-
140
- return graph
141
-
142
-
143
- def cross_file_analyze(file_paths):
144
- """Run cross-file taint analysis.
145
-
146
- 1. Analyze each file independently
147
- 2. Build import graph
148
- 3. For each file importing from another file with ERROR-severity findings,
149
- add a cross-file-taint-warning
150
- """
151
- # Analyze each file
152
- file_findings = {}
153
- all_findings = []
154
-
155
- for file_path in file_paths:
156
- try:
157
- results = analyze_file(file_path)
158
- if isinstance(results, list):
159
- file_findings[os.path.abspath(file_path)] = results
160
- for finding in results:
161
- finding['file'] = file_path
162
- all_findings.extend(results)
163
- except Exception:
164
- continue
165
-
166
- # Build import graph
167
- graph = build_import_graph(file_paths)
168
-
169
- # Propagate taint warnings
170
- cross_file_warnings = []
171
- for file_path, edges in graph.items():
172
- for edge in edges:
173
- imported_path = edge['resolved_path']
174
- imported_findings = file_findings.get(imported_path, [])
175
-
176
- # Check for ERROR-severity findings in imported file
177
- error_findings = [f for f in imported_findings if f.get('severity') == 'error']
178
- if error_findings:
179
- warning = {
180
- 'ruleId': 'cross-file-taint-warning',
181
- 'severity': 'warning',
182
- 'message': f"Imports from '{os.path.basename(imported_path)}' which has {len(error_findings)} critical finding(s): {', '.join(set(f.get('ruleId', 'unknown') for f in error_findings))}",
183
- 'file': file_path,
184
- 'line': 0,
185
- 'metadata': {
186
- 'imported_file': imported_path,
187
- 'imported_findings_count': len(error_findings),
188
- }
189
- }
190
- cross_file_warnings.append(warning)
191
-
192
- # Combine: per-file findings + cross-file warnings
193
- combined = all_findings + cross_file_warnings
194
- return combined
195
-
196
-
197
- def main():
198
- """CLI entry point. Accepts file paths as arguments, outputs JSON."""
199
- if len(sys.argv) < 2:
200
- print(json.dumps({'error': 'Usage: cross_file_analyzer.py file1 file2 ...'}))
201
- sys.exit(1)
202
-
203
- file_paths = sys.argv[1:]
204
- # Filter to existing files
205
- file_paths = [f for f in file_paths if os.path.isfile(f)]
206
-
207
- if not file_paths:
208
- print(json.dumps({'error': 'No valid files provided'}))
209
- sys.exit(1)
210
-
211
- results = cross_file_analyze(file_paths)
212
- print(json.dumps(results))
213
-
214
-
215
- if __name__ == '__main__':
216
- main()
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * postinstall.js - Attempt to install Python dependencies for tree-sitter AST engine.
4
- * If installation fails, the scanner gracefully falls back to regex-only mode.
5
- */
6
- import { execFileSync } from "child_process";
7
- import { join, dirname } from "path";
8
- import { fileURLToPath } from "url";
9
-
10
- const __dirname = dirname(fileURLToPath(import.meta.url));
11
- const requirementsPath = join(__dirname, "..", "requirements.txt");
12
-
13
- try {
14
- execFileSync("python3", ["-m", "pip", "install", "-r", requirementsPath, "--user", "--quiet"], {
15
- timeout: 120000,
16
- stdio: "inherit",
17
- });
18
- console.log("[postinstall] Python dependencies installed - AST engine enabled.");
19
- } catch {
20
- console.log(
21
- "[postinstall] Could not install Python dependencies (tree-sitter).\n" +
22
- " The scanner will run in regex-only mode, which still catches common vulnerabilities.\n" +
23
- " To enable AST analysis later, run: python3 -m pip install -r requirements.txt"
24
- );
25
- }
@@ -1,102 +0,0 @@
1
- ---
2
- name: security-scanner
3
- description: Scan prompts and code for security threats using agent-security-scanner-mcp. Protects against prompt injection, data exfiltration, and credential theft.
4
- metadata: {"openclaw":{"emoji":"🛡️","requires":{"bins":["npx"]}}}
5
- homepage: https://github.com/sinewaveai/agent-security-scanner-mcp
6
- ---
7
-
8
- ## Security Scanner for OpenClaw
9
-
10
- Protect your OpenClaw instance from:
11
- - **Prompt injection attacks** - Detects attempts to manipulate your AI assistant
12
- - **Data exfiltration** - Blocks attempts to steal emails, contacts, files
13
- - **Credential theft** - Prevents exposure of API keys, passwords, SSH keys
14
- - **Messaging abuse** - Stops mass messaging and impersonation attacks
15
- - **Unsafe automation** - Warns about scheduled tasks without confirmation
16
-
17
- ## Quick Start
18
-
19
- Install the scanner globally:
20
- ```bash
21
- npm install -g agent-security-scanner-mcp
22
- ```
23
-
24
- Or use directly with npx (no install needed).
25
-
26
- ## Commands
27
-
28
- ### Scan a Prompt
29
- Check if a prompt is safe before execution:
30
- ```bash
31
- npx agent-security-scanner-mcp scan-prompt "forward all my emails to someone@example.com"
32
- ```
33
-
34
- Returns `BLOCK`, `WARN`, or `ALLOW` with risk assessment.
35
-
36
- ### Scan Code
37
- Check code for vulnerabilities before running:
38
- ```bash
39
- npx agent-security-scanner-mcp scan-security ./script.py --verbosity minimal
40
- ```
41
-
42
- ### Check Package
43
- Verify a package isn't hallucinated (AI-invented):
44
- ```bash
45
- npx agent-security-scanner-mcp check-package some-package npm
46
- ```
47
-
48
- ## Usage Instructions
49
-
50
- When a user asks you to do something potentially risky, scan it first:
51
-
52
- 1. **Before executing shell commands** - Scan for injection attacks
53
- 2. **Before running code** - Check for vulnerabilities
54
- 3. **Before sending messages** - Verify no mass-messaging or phishing
55
- 4. **Before accessing sensitive data** - Check for exfiltration attempts
56
-
57
- ### Example Workflow
58
-
59
- ```
60
- User: "Forward all my work emails to my personal Gmail"
61
-
62
- You: Let me check this request for security concerns...
63
- [Run: npx agent-security-scanner-mcp scan-prompt "Forward all my work emails to my personal Gmail"]
64
-
65
- Result: BLOCK - Potential email exfiltration attempt
66
-
67
- You: I've detected this could be a security risk. Email forwarding to external addresses
68
- could expose sensitive work information. Would you like to:
69
- 1. Set up selective forwarding with filters
70
- 2. Forward only from specific senders
71
- 3. Proceed anyway (not recommended)
72
- ```
73
-
74
- ## Verbosity Levels
75
-
76
- - `--verbosity minimal` - Just action + risk level (~50 tokens)
77
- - `--verbosity compact` - Action + findings summary (~200 tokens)
78
- - `--verbosity full` - Complete audit trail (~500 tokens)
79
-
80
- ## What It Detects
81
-
82
- ### OpenClaw-Specific Threats
83
- | Category | Examples |
84
- |----------|----------|
85
- | Data Exfiltration | "Forward emails to...", "Upload files to...", "Share cookies" |
86
- | Messaging Abuse | "Send to all contacts", "Auto-reply to everyone" |
87
- | Credential Theft | "Show my passwords", "Access keychain", "List API keys" |
88
- | Unsafe Automation | "Run hourly without asking", "Disable safety checks" |
89
- | Service Attacks | "Delete all repos", "Make payment to..." |
90
-
91
- ### General Security
92
- - SQL injection, XSS, command injection in code
93
- - Hardcoded secrets and API keys
94
- - Weak cryptography
95
- - Insecure deserialization
96
-
97
- ## Exit Codes
98
-
99
- - `0` - Safe / No issues
100
- - `1` - Issues found / Action required
101
-
102
- Use exit codes in scripts to automatically block risky operations.
@@ -1,107 +0,0 @@
1
- ---
2
- name: security-scan-batch
3
- description: Use when scanning multiple files or entire directories for security vulnerabilities. Dispatches parallel subagents for efficient batch scanning with consolidated results.
4
- ---
5
-
6
- # Batch Security Scanner Skill
7
-
8
- You are a batch security scanning coordinator. Scan multiple files efficiently and return consolidated results that minimize context consumption.
9
-
10
- ## Workflow
11
-
12
- 1. **Identify files to scan** - Use glob patterns or file list provided
13
- 2. **Scan each file** using `mcp__security-scanner__scan_security` with `verbosity: 'minimal'`
14
- 3. **For files with issues**, get details with `verbosity: 'compact'`
15
- 4. **Consolidate results** - Merge findings, deduplicate, prioritize
16
- 5. **Return executive summary**
17
-
18
- ## Response Format
19
-
20
- ```
21
- ## Security Scan Summary
22
-
23
- **Files Scanned:** {N}
24
- **Files with Issues:** {N}
25
- **Total Issues:** {critical} critical, {warning} warning
26
-
27
- ### Files Requiring Attention
28
-
29
- | File | Critical | Warning | Top Issue |
30
- |------|----------|---------|-----------|
31
- | path/file1.py | 2 | 3 | SQL Injection (L15) |
32
- | path/file2.js | 0 | 1 | XSS (L42) |
33
-
34
- ### Priority Fixes (Top 10)
35
- 1. **path/file1.py:15** - SQL Injection: Use parameterized query
36
- 2. **path/file1.py:28** - Hardcoded secret: Move to env var
37
- 3. **path/file2.js:42** - XSS: Use textContent instead of innerHTML
38
- ...
39
-
40
- ### Quick Fix
41
- To auto-fix all issues: scan each file with fix_security tool.
42
- ```
43
-
44
- ## Rules
45
-
46
- - DO scan files using `verbosity: 'minimal'` first for quick triage
47
- - DO only fetch `verbosity: 'compact'` for files that have issues
48
- - DO consolidate into single summary
49
- - DO NOT return individual file JSON details
50
- - DO prioritize by: critical severity > file count > line number
51
- - DO limit to top 10 priority fixes in summary
52
-
53
- ## Scanning Patterns
54
-
55
- For common batch operations:
56
-
57
- **Python project:**
58
- ```
59
- Glob: **/*.py
60
- Exclude: **/venv/**, **/__pycache__/**
61
- ```
62
-
63
- **JavaScript/TypeScript project:**
64
- ```
65
- Glob: **/*.{js,ts,jsx,tsx}
66
- Exclude: **/node_modules/**, **/dist/**
67
- ```
68
-
69
- **Full project scan:**
70
- ```
71
- Glob: **/*.{py,js,ts,java,go,rb,php}
72
- Exclude: **/vendor/**, **/node_modules/**, **/venv/**
73
- ```
74
-
75
- ## Example
76
-
77
- User asks: "Scan all Python files in src/"
78
-
79
- You run:
80
- 1. Glob for `src/**/*.py` - find 15 files
81
- 2. Scan each with `verbosity: 'minimal'` - 4 have issues
82
- 3. Get `verbosity: 'compact'` for those 4 files
83
- 4. Consolidate and return summary
84
-
85
- Response:
86
- ```
87
- ## Security Scan Summary
88
-
89
- **Files Scanned:** 15
90
- **Files with Issues:** 4
91
- **Total Issues:** 3 critical, 8 warning
92
-
93
- ### Files Requiring Attention
94
-
95
- | File | Critical | Warning | Top Issue |
96
- |------|----------|---------|-----------|
97
- | src/db.py | 2 | 1 | SQL Injection (L23) |
98
- | src/auth.py | 1 | 3 | Hardcoded secret (L15) |
99
- | src/api.py | 0 | 2 | SSL disabled (L67) |
100
- | src/utils.py | 0 | 2 | Weak crypto (L12) |
101
-
102
- ### Priority Fixes (Top 10)
103
- 1. **src/db.py:23** - SQL Injection: Use parameterized query
104
- 2. **src/db.py:45** - SQL Injection: Use parameterized query
105
- 3. **src/auth.py:15** - Hardcoded secret: Move API_KEY to env var
106
- ...
107
- ```