claude-mpm 4.4.3__py3-none-any.whl → 4.4.4__py3-none-any.whl

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.
Files changed (118) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/agent_loader.py +3 -2
  3. claude_mpm/agents/agent_loader_integration.py +2 -1
  4. claude_mpm/agents/async_agent_loader.py +2 -2
  5. claude_mpm/agents/base_agent_loader.py +2 -2
  6. claude_mpm/agents/frontmatter_validator.py +1 -0
  7. claude_mpm/agents/system_agent_config.py +2 -1
  8. claude_mpm/cli/commands/doctor.py +44 -5
  9. claude_mpm/cli/commands/mpm_init.py +116 -62
  10. claude_mpm/cli/parsers/configure_parser.py +3 -1
  11. claude_mpm/cli/startup_logging.py +1 -3
  12. claude_mpm/config/agent_config.py +1 -1
  13. claude_mpm/config/paths.py +2 -1
  14. claude_mpm/core/agent_name_normalizer.py +1 -0
  15. claude_mpm/core/config.py +2 -1
  16. claude_mpm/core/config_aliases.py +2 -1
  17. claude_mpm/core/file_utils.py +0 -1
  18. claude_mpm/core/framework/__init__.py +6 -6
  19. claude_mpm/core/framework/formatters/__init__.py +2 -2
  20. claude_mpm/core/framework/formatters/capability_generator.py +19 -8
  21. claude_mpm/core/framework/formatters/content_formatter.py +8 -3
  22. claude_mpm/core/framework/formatters/context_generator.py +7 -3
  23. claude_mpm/core/framework/loaders/__init__.py +3 -3
  24. claude_mpm/core/framework/loaders/agent_loader.py +7 -3
  25. claude_mpm/core/framework/loaders/file_loader.py +16 -6
  26. claude_mpm/core/framework/loaders/instruction_loader.py +16 -6
  27. claude_mpm/core/framework/loaders/packaged_loader.py +36 -12
  28. claude_mpm/core/framework/processors/__init__.py +2 -2
  29. claude_mpm/core/framework/processors/memory_processor.py +14 -6
  30. claude_mpm/core/framework/processors/metadata_processor.py +5 -5
  31. claude_mpm/core/framework/processors/template_processor.py +12 -6
  32. claude_mpm/core/framework_loader.py +44 -20
  33. claude_mpm/core/log_manager.py +2 -1
  34. claude_mpm/core/tool_access_control.py +1 -0
  35. claude_mpm/core/unified_agent_registry.py +2 -1
  36. claude_mpm/core/unified_paths.py +1 -0
  37. claude_mpm/experimental/cli_enhancements.py +1 -0
  38. claude_mpm/hooks/base_hook.py +1 -0
  39. claude_mpm/hooks/instruction_reinforcement.py +1 -0
  40. claude_mpm/hooks/kuzu_memory_hook.py +20 -13
  41. claude_mpm/hooks/validation_hooks.py +1 -1
  42. claude_mpm/scripts/mpm_doctor.py +1 -0
  43. claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
  44. claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
  45. claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
  46. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
  47. claude_mpm/services/agents/management/agent_management_service.py +1 -1
  48. claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
  49. claude_mpm/services/agents/memory/memory_file_service.py +6 -2
  50. claude_mpm/services/agents/memory/memory_format_service.py +0 -1
  51. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  52. claude_mpm/services/async_session_logger.py +1 -1
  53. claude_mpm/services/claude_session_logger.py +1 -0
  54. claude_mpm/services/core/path_resolver.py +1 -0
  55. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  56. claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
  57. claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
  58. claude_mpm/services/diagnostics/diagnostic_runner.py +3 -0
  59. claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
  60. claude_mpm/services/event_bus/direct_relay.py +2 -1
  61. claude_mpm/services/event_bus/event_bus.py +1 -0
  62. claude_mpm/services/event_bus/relay.py +3 -2
  63. claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
  64. claude_mpm/services/infrastructure/daemon_manager.py +1 -1
  65. claude_mpm/services/mcp_config_manager.py +10 -10
  66. claude_mpm/services/mcp_gateway/core/process_pool.py +62 -23
  67. claude_mpm/services/mcp_gateway/tools/__init__.py +6 -5
  68. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +3 -1
  69. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +16 -31
  70. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  71. claude_mpm/services/project/archive_manager.py +159 -96
  72. claude_mpm/services/project/documentation_manager.py +64 -45
  73. claude_mpm/services/project/enhanced_analyzer.py +132 -89
  74. claude_mpm/services/project/project_organizer.py +225 -131
  75. claude_mpm/services/response_tracker.py +1 -1
  76. claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
  77. claude_mpm/services/unified/__init__.py +1 -1
  78. claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
  79. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
  80. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
  81. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
  82. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
  83. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
  84. claude_mpm/services/unified/config_strategies/__init__.py +111 -126
  85. claude_mpm/services/unified/config_strategies/config_schema.py +157 -111
  86. claude_mpm/services/unified/config_strategies/context_strategy.py +91 -89
  87. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +183 -173
  88. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +160 -152
  89. claude_mpm/services/unified/config_strategies/unified_config_service.py +124 -112
  90. claude_mpm/services/unified/config_strategies/validation_strategy.py +298 -259
  91. claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
  92. claude_mpm/services/unified/deployment_strategies/base.py +24 -28
  93. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
  94. claude_mpm/services/unified/deployment_strategies/local.py +49 -34
  95. claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
  96. claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
  97. claude_mpm/services/unified/interfaces.py +0 -26
  98. claude_mpm/services/unified/migration.py +17 -40
  99. claude_mpm/services/unified/strategies.py +9 -26
  100. claude_mpm/services/unified/unified_analyzer.py +48 -44
  101. claude_mpm/services/unified/unified_config.py +21 -19
  102. claude_mpm/services/unified/unified_deployment.py +21 -26
  103. claude_mpm/storage/state_storage.py +1 -0
  104. claude_mpm/utils/agent_dependency_loader.py +18 -6
  105. claude_mpm/utils/common.py +14 -12
  106. claude_mpm/utils/database_connector.py +15 -12
  107. claude_mpm/utils/error_handler.py +1 -0
  108. claude_mpm/utils/log_cleanup.py +1 -0
  109. claude_mpm/utils/path_operations.py +1 -0
  110. claude_mpm/utils/session_logging.py +1 -1
  111. claude_mpm/utils/subprocess_utils.py +1 -0
  112. claude_mpm/validation/agent_validator.py +1 -1
  113. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +9 -3
  114. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +118 -117
  115. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
  116. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
  117. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
  118. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/top_level.txt +0 -0
@@ -12,11 +12,16 @@ Created: 2025-01-26
12
12
  import ast
13
13
  import re
14
14
  from pathlib import Path
15
- from typing import Any, Dict, List, Optional, Set, Tuple
15
+ from typing import Any, Dict, List, Optional
16
16
 
17
17
  from claude_mpm.core.logging_utils import get_logger
18
18
 
19
- from ..strategies import AnalyzerStrategy, StrategyContext, StrategyMetadata, StrategyPriority
19
+ from ..strategies import (
20
+ AnalyzerStrategy,
21
+ StrategyContext,
22
+ StrategyMetadata,
23
+ StrategyPriority,
24
+ )
20
25
 
21
26
  logger = get_logger(__name__)
22
27
 
@@ -38,7 +43,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
38
43
  "sql_injection": {
39
44
  "patterns": [
40
45
  r'(execute|query)\s*\(\s*["\'].*%[s|d].*["\'].*%',
41
- r'(execute|query)\s*\(\s*.*\+.*\)',
46
+ r"(execute|query)\s*\(\s*.*\+.*\)",
42
47
  r'f["\'].*SELECT.*{.*}.*FROM',
43
48
  ],
44
49
  "severity": "critical",
@@ -55,35 +60,35 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
55
60
  },
56
61
  "weak_crypto": {
57
62
  "patterns": [
58
- r'(MD5|SHA1)\s*\(',
59
- r'DES\s*\(',
60
- r'Random\(\)(?!\.SystemRandom)',
63
+ r"(MD5|SHA1)\s*\(",
64
+ r"DES\s*\(",
65
+ r"Random\(\)(?!\.SystemRandom)",
61
66
  ],
62
67
  "severity": "medium",
63
68
  "description": "Weak cryptographic algorithm usage",
64
69
  },
65
70
  "command_injection": {
66
71
  "patterns": [
67
- r'os\.(system|popen|spawn.*)\s*\([^)]*\+[^)]*\)',
68
- r'subprocess\.(run|call|Popen)\s*\([^)]*shell\s*=\s*True',
69
- r'eval\s*\([^)]*input\s*\(',
72
+ r"os\.(system|popen|spawn.*)\s*\([^)]*\+[^)]*\)",
73
+ r"subprocess\.(run|call|Popen)\s*\([^)]*shell\s*=\s*True",
74
+ r"eval\s*\([^)]*input\s*\(",
70
75
  ],
71
76
  "severity": "critical",
72
77
  "description": "Potential command injection vulnerability",
73
78
  },
74
79
  "path_traversal": {
75
80
  "patterns": [
76
- r'open\s*\([^)]*\.\.[/\\]',
77
- r'(read_file|write_file)\s*\([^)]*user_input',
78
- r'Path\s*\([^)]*\+[^)]*\)',
81
+ r"open\s*\([^)]*\.\.[/\\]",
82
+ r"(read_file|write_file)\s*\([^)]*user_input",
83
+ r"Path\s*\([^)]*\+[^)]*\)",
79
84
  ],
80
85
  "severity": "high",
81
86
  "description": "Potential path traversal vulnerability",
82
87
  },
83
88
  "xss": {
84
89
  "patterns": [
85
- r'innerHTML\s*=\s*[^;]*user',
86
- r'document\.write\s*\([^)]*user',
90
+ r"innerHTML\s*=\s*[^;]*user",
91
+ r"document\.write\s*\([^)]*user",
87
92
  r'v-html\s*=\s*["\'][^"\']*user',
88
93
  ],
89
94
  "severity": "high",
@@ -95,27 +100,27 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
95
100
  CONFIG_ISSUES = {
96
101
  "debug_enabled": {
97
102
  "patterns": [
98
- r'DEBUG\s*=\s*True',
99
- r'debug\s*:\s*true',
100
- r'app\.debug\s*=\s*True',
103
+ r"DEBUG\s*=\s*True",
104
+ r"debug\s*:\s*true",
105
+ r"app\.debug\s*=\s*True",
101
106
  ],
102
107
  "severity": "medium",
103
108
  "description": "Debug mode enabled in production configuration",
104
109
  },
105
110
  "insecure_cors": {
106
111
  "patterns": [
107
- r'Access-Control-Allow-Origin.*\*',
112
+ r"Access-Control-Allow-Origin.*\*",
108
113
  r'cors\s*\(.*origin\s*:\s*["\'].*\*',
109
- r'CORS_ORIGIN_ALLOW_ALL\s*=\s*True',
114
+ r"CORS_ORIGIN_ALLOW_ALL\s*=\s*True",
110
115
  ],
111
116
  "severity": "medium",
112
117
  "description": "Insecure CORS configuration allowing all origins",
113
118
  },
114
119
  "missing_csrf": {
115
120
  "patterns": [
116
- r'csrf_enabled\s*=\s*False',
117
- r'CSRF_ENABLED\s*=\s*False',
118
- r'@csrf_exempt',
121
+ r"csrf_enabled\s*=\s*False",
122
+ r"CSRF_ENABLED\s*=\s*False",
123
+ r"@csrf_exempt",
119
124
  ],
120
125
  "severity": "high",
121
126
  "description": "CSRF protection disabled",
@@ -189,7 +194,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
189
194
 
190
195
  if target_path.is_file():
191
196
  return self._analyze_file(target_path, options)
192
- elif target_path.is_dir():
197
+ if target_path.is_dir():
193
198
  return self._analyze_directory(target_path, options)
194
199
 
195
200
  return {
@@ -227,7 +232,9 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
227
232
  results["vulnerabilities"].extend(js_issues)
228
233
 
229
234
  # Calculate risk score
230
- results["risk_score"] = self._calculate_risk_score(results["vulnerabilities"])
235
+ results["risk_score"] = self._calculate_risk_score(
236
+ results["vulnerabilities"]
237
+ )
231
238
 
232
239
  # Add summary
233
240
  results["summary"] = self._generate_summary(results["vulnerabilities"])
@@ -239,7 +246,9 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
239
246
 
240
247
  return results
241
248
 
242
- def _analyze_directory(self, dir_path: Path, options: Dict[str, Any]) -> Dict[str, Any]:
249
+ def _analyze_directory(
250
+ self, dir_path: Path, options: Dict[str, Any]
251
+ ) -> Dict[str, Any]:
243
252
  """Analyze all files in a directory for security issues."""
244
253
  results = {
245
254
  "status": "success",
@@ -254,10 +263,29 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
254
263
 
255
264
  # Define file extensions to analyze
256
265
  analyzable_extensions = {
257
- ".py", ".js", ".jsx", ".ts", ".tsx", ".java", ".cs",
258
- ".php", ".rb", ".go", ".rs", ".cpp", ".c", ".h",
259
- ".yml", ".yaml", ".json", ".xml", ".conf", ".config",
260
- ".env", ".ini", ".properties",
266
+ ".py",
267
+ ".js",
268
+ ".jsx",
269
+ ".ts",
270
+ ".tsx",
271
+ ".java",
272
+ ".cs",
273
+ ".php",
274
+ ".rb",
275
+ ".go",
276
+ ".rs",
277
+ ".cpp",
278
+ ".c",
279
+ ".h",
280
+ ".yml",
281
+ ".yaml",
282
+ ".json",
283
+ ".xml",
284
+ ".conf",
285
+ ".config",
286
+ ".env",
287
+ ".ini",
288
+ ".properties",
261
289
  }
262
290
 
263
291
  # Analyze each file
@@ -272,16 +300,22 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
272
300
  continue
273
301
 
274
302
  file_result = self._analyze_file(file_path, options)
275
- if file_result["status"] == "success" and file_result["vulnerabilities"]:
303
+ if (
304
+ file_result["status"] == "success"
305
+ and file_result["vulnerabilities"]
306
+ ):
276
307
  results["files"].append(file_result)
277
308
  results["files_analyzed"] += 1
278
- results["total_vulnerabilities"] += len(file_result["vulnerabilities"])
309
+ results["total_vulnerabilities"] += len(
310
+ file_result["vulnerabilities"]
311
+ )
279
312
 
280
313
  # Count by severity
281
314
  for vuln in file_result["vulnerabilities"]:
282
315
  severity = vuln.get("severity", "unknown")
283
- results["vulnerabilities_by_severity"][severity] = \
316
+ results["vulnerabilities_by_severity"][severity] = (
284
317
  results["vulnerabilities_by_severity"].get(severity, 0) + 1
318
+ )
285
319
 
286
320
  # Calculate overall risk score
287
321
  results["risk_score"] = self._calculate_overall_risk(results)
@@ -291,7 +325,9 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
291
325
 
292
326
  return results
293
327
 
294
- def _scan_for_vulnerabilities(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
328
+ def _scan_for_vulnerabilities(
329
+ self, content: str, file_path: Path
330
+ ) -> List[Dict[str, Any]]:
295
331
  """Scan content for known vulnerability patterns."""
296
332
  vulnerabilities = []
297
333
 
@@ -299,48 +335,67 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
299
335
  for pattern in vuln_info["patterns"]:
300
336
  matches = re.finditer(pattern, content, re.IGNORECASE | re.MULTILINE)
301
337
  for match in matches:
302
- line_num = content[:match.start()].count("\n") + 1
303
-
304
- vulnerabilities.append({
305
- "type": vuln_type,
306
- "severity": vuln_info["severity"],
307
- "description": vuln_info["description"],
308
- "file": str(file_path),
309
- "line": line_num,
310
- "code": match.group(0)[:100], # Truncate long matches
311
- "pattern": pattern,
312
- })
338
+ line_num = content[: match.start()].count("\n") + 1
339
+
340
+ vulnerabilities.append(
341
+ {
342
+ "type": vuln_type,
343
+ "severity": vuln_info["severity"],
344
+ "description": vuln_info["description"],
345
+ "file": str(file_path),
346
+ "line": line_num,
347
+ "code": match.group(0)[:100], # Truncate long matches
348
+ "pattern": pattern,
349
+ }
350
+ )
313
351
 
314
352
  return vulnerabilities
315
353
 
316
- def _scan_for_config_issues(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
354
+ def _scan_for_config_issues(
355
+ self, content: str, file_path: Path
356
+ ) -> List[Dict[str, Any]]:
317
357
  """Scan for insecure configuration patterns."""
318
358
  issues = []
319
359
 
320
360
  # Only check configuration files
321
- config_extensions = {".yml", ".yaml", ".json", ".conf", ".config", ".ini", ".env"}
322
- if file_path.suffix not in config_extensions and \
323
- file_path.name not in ["settings.py", "config.py", "configuration.py"]:
361
+ config_extensions = {
362
+ ".yml",
363
+ ".yaml",
364
+ ".json",
365
+ ".conf",
366
+ ".config",
367
+ ".ini",
368
+ ".env",
369
+ }
370
+ if file_path.suffix not in config_extensions and file_path.name not in [
371
+ "settings.py",
372
+ "config.py",
373
+ "configuration.py",
374
+ ]:
324
375
  return issues
325
376
 
326
377
  for issue_type, issue_info in self.CONFIG_ISSUES.items():
327
378
  for pattern in issue_info["patterns"]:
328
379
  matches = re.finditer(pattern, content, re.IGNORECASE | re.MULTILINE)
329
380
  for match in matches:
330
- line_num = content[:match.start()].count("\n") + 1
331
-
332
- issues.append({
333
- "type": f"config_{issue_type}",
334
- "severity": issue_info["severity"],
335
- "description": issue_info["description"],
336
- "file": str(file_path),
337
- "line": line_num,
338
- "code": match.group(0),
339
- })
381
+ line_num = content[: match.start()].count("\n") + 1
382
+
383
+ issues.append(
384
+ {
385
+ "type": f"config_{issue_type}",
386
+ "severity": issue_info["severity"],
387
+ "description": issue_info["description"],
388
+ "file": str(file_path),
389
+ "line": line_num,
390
+ "code": match.group(0),
391
+ }
392
+ )
340
393
 
341
394
  return issues
342
395
 
343
- def _analyze_python_security(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
396
+ def _analyze_python_security(
397
+ self, content: str, file_path: Path
398
+ ) -> List[Dict[str, Any]]:
344
399
  """Perform Python-specific security analysis."""
345
400
  issues = []
346
401
 
@@ -362,33 +417,41 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
362
417
  if isinstance(node.func, ast.Name):
363
418
  func_name = node.func.id
364
419
  if func_name in dangerous_functions:
365
- issues.append({
366
- "type": "dangerous_function",
367
- "severity": dangerous_functions[func_name],
368
- "description": f"Use of dangerous function: {func_name}",
369
- "file": str(file_path),
370
- "line": node.lineno,
371
- "code": func_name,
372
- })
420
+ issues.append(
421
+ {
422
+ "type": "dangerous_function",
423
+ "severity": dangerous_functions[func_name],
424
+ "description": f"Use of dangerous function: {func_name}",
425
+ "file": str(file_path),
426
+ "line": node.lineno,
427
+ "code": func_name,
428
+ }
429
+ )
373
430
 
374
431
  # Check for subprocess with shell=True
375
432
  elif isinstance(node.func, ast.Attribute):
376
- if (hasattr(node.func.value, "id") and
377
- node.func.value.id == "subprocess" and
378
- node.func.attr in ["run", "call", "Popen"]):
433
+ if (
434
+ hasattr(node.func.value, "id")
435
+ and node.func.value.id == "subprocess"
436
+ and node.func.attr in ["run", "call", "Popen"]
437
+ ):
379
438
 
380
439
  for keyword in node.keywords:
381
- if keyword.arg == "shell" and \
382
- isinstance(keyword.value, ast.Constant) and \
383
- keyword.value.value is True:
384
- issues.append({
385
- "type": "shell_injection",
386
- "severity": "critical",
387
- "description": "subprocess with shell=True is vulnerable to injection",
388
- "file": str(file_path),
389
- "line": node.lineno,
390
- "code": "subprocess with shell=True",
391
- })
440
+ if (
441
+ keyword.arg == "shell"
442
+ and isinstance(keyword.value, ast.Constant)
443
+ and keyword.value.value is True
444
+ ):
445
+ issues.append(
446
+ {
447
+ "type": "shell_injection",
448
+ "severity": "critical",
449
+ "description": "subprocess with shell=True is vulnerable to injection",
450
+ "file": str(file_path),
451
+ "line": node.lineno,
452
+ "code": "subprocess with shell=True",
453
+ }
454
+ )
392
455
 
393
456
  self.generic_visit(node)
394
457
 
@@ -401,24 +464,26 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
401
464
 
402
465
  return issues
403
466
 
404
- def _analyze_javascript_security(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
467
+ def _analyze_javascript_security(
468
+ self, content: str, file_path: Path
469
+ ) -> List[Dict[str, Any]]:
405
470
  """Perform JavaScript-specific security analysis."""
406
471
  issues = []
407
472
 
408
473
  # Check for dangerous JavaScript patterns
409
474
  js_patterns = {
410
475
  "eval_usage": {
411
- "pattern": r'\beval\s*\(',
476
+ "pattern": r"\beval\s*\(",
412
477
  "severity": "critical",
413
478
  "description": "Use of eval() is dangerous and should be avoided",
414
479
  },
415
480
  "innerhtml": {
416
- "pattern": r'\.innerHTML\s*=',
481
+ "pattern": r"\.innerHTML\s*=",
417
482
  "severity": "high",
418
483
  "description": "Direct innerHTML assignment can lead to XSS",
419
484
  },
420
485
  "document_write": {
421
- "pattern": r'document\.write\s*\(',
486
+ "pattern": r"document\.write\s*\(",
422
487
  "severity": "medium",
423
488
  "description": "document.write() can be dangerous with user input",
424
489
  },
@@ -432,16 +497,18 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
432
497
  for issue_type, issue_info in js_patterns.items():
433
498
  matches = re.finditer(issue_info["pattern"], content, re.IGNORECASE)
434
499
  for match in matches:
435
- line_num = content[:match.start()].count("\n") + 1
500
+ line_num = content[: match.start()].count("\n") + 1
436
501
 
437
- issues.append({
438
- "type": f"js_{issue_type}",
439
- "severity": issue_info["severity"],
440
- "description": issue_info["description"],
441
- "file": str(file_path),
442
- "line": line_num,
443
- "code": match.group(0),
444
- })
502
+ issues.append(
503
+ {
504
+ "type": f"js_{issue_type}",
505
+ "severity": issue_info["severity"],
506
+ "description": issue_info["description"],
507
+ "file": str(file_path),
508
+ "line": line_num,
509
+ "code": match.group(0),
510
+ }
511
+ )
445
512
 
446
513
  return issues
447
514
 
@@ -492,7 +559,9 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
492
559
  # Normalize and cap at 100
493
560
  return min(100.0, round(weighted_score / max(results["files_analyzed"], 1), 2))
494
561
 
495
- def _generate_summary(self, vulnerabilities: List[Dict[str, Any]]) -> Dict[str, Any]:
562
+ def _generate_summary(
563
+ self, vulnerabilities: List[Dict[str, Any]]
564
+ ) -> Dict[str, Any]:
496
565
  """Generate summary of security findings."""
497
566
  summary = {
498
567
  "total": len(vulnerabilities),
@@ -504,7 +573,9 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
504
573
  for vuln in vulnerabilities:
505
574
  # Count by severity
506
575
  severity = vuln.get("severity", "unknown")
507
- summary["by_severity"][severity] = summary["by_severity"].get(severity, 0) + 1
576
+ summary["by_severity"][severity] = (
577
+ summary["by_severity"].get(severity, 0) + 1
578
+ )
508
579
 
509
580
  # Count by type
510
581
  vuln_type = vuln.get("type", "unknown")
@@ -529,9 +600,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
529
600
  )
530
601
 
531
602
  if vuln_by_severity.get("high", 0) > 0:
532
- recommendations.append(
533
- "Prioritize fixing high-severity vulnerabilities"
534
- )
603
+ recommendations.append("Prioritize fixing high-severity vulnerabilities")
535
604
 
536
605
  # Type-specific recommendations
537
606
  if results["files"]:
@@ -566,7 +635,9 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
566
635
  )
567
636
 
568
637
  if not recommendations:
569
- recommendations.append("No critical security issues found. Continue with regular security audits.")
638
+ recommendations.append(
639
+ "No critical security issues found. Continue with regular security audits."
640
+ )
570
641
 
571
642
  return recommendations
572
643
 
@@ -578,10 +649,12 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
578
649
  return metrics
579
650
 
580
651
  if analysis_result.get("type") == "file":
581
- metrics.update({
582
- "vulnerabilities": len(analysis_result.get("vulnerabilities", [])),
583
- "risk_score": analysis_result.get("risk_score", 0),
584
- })
652
+ metrics.update(
653
+ {
654
+ "vulnerabilities": len(analysis_result.get("vulnerabilities", [])),
655
+ "risk_score": analysis_result.get("risk_score", 0),
656
+ }
657
+ )
585
658
 
586
659
  # Count by severity
587
660
  for vuln in analysis_result.get("vulnerabilities", []):
@@ -590,14 +663,20 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
590
663
  metrics[key] = metrics.get(key, 0) + 1
591
664
 
592
665
  elif analysis_result.get("type") == "directory":
593
- metrics.update({
594
- "files_analyzed": analysis_result.get("files_analyzed", 0),
595
- "total_vulnerabilities": analysis_result.get("total_vulnerabilities", 0),
596
- "risk_score": analysis_result.get("risk_score", 0),
597
- })
666
+ metrics.update(
667
+ {
668
+ "files_analyzed": analysis_result.get("files_analyzed", 0),
669
+ "total_vulnerabilities": analysis_result.get(
670
+ "total_vulnerabilities", 0
671
+ ),
672
+ "risk_score": analysis_result.get("risk_score", 0),
673
+ }
674
+ )
598
675
 
599
676
  # Add severity breakdown
600
- for severity, count in analysis_result.get("vulnerabilities_by_severity", {}).items():
677
+ for severity, count in analysis_result.get(
678
+ "vulnerabilities_by_severity", {}
679
+ ).items():
601
680
  metrics[f"severity_{severity}"] = count
602
681
 
603
682
  return metrics
@@ -624,7 +703,12 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
624
703
  baseline_metrics = self.extract_metrics(baseline)
625
704
  current_metrics = self.extract_metrics(current)
626
705
 
627
- for key in ["severity_critical", "severity_high", "severity_medium", "severity_low"]:
706
+ for key in [
707
+ "severity_critical",
708
+ "severity_high",
709
+ "severity_medium",
710
+ "severity_low",
711
+ ]:
628
712
  baseline_count = baseline_metrics.get(key, 0)
629
713
  current_count = current_metrics.get(key, 0)
630
714
 
@@ -658,4 +742,4 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
658
742
  f"Vulnerabilities increased from {total_baseline} to {total_current}"
659
743
  )
660
744
 
661
- return comparison
745
+ return comparison