codeframe-ai 0.9.0__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 (197) hide show
  1. codeframe/__init__.py +11 -0
  2. codeframe/__main__.py +20 -0
  3. codeframe/adapters/__init__.py +5 -0
  4. codeframe/adapters/e2b/__init__.py +13 -0
  5. codeframe/adapters/e2b/adapter.py +342 -0
  6. codeframe/adapters/e2b/budget.py +71 -0
  7. codeframe/adapters/e2b/credential_scanner.py +134 -0
  8. codeframe/adapters/llm/__init__.py +92 -0
  9. codeframe/adapters/llm/anthropic.py +414 -0
  10. codeframe/adapters/llm/base.py +444 -0
  11. codeframe/adapters/llm/mock.py +281 -0
  12. codeframe/adapters/llm/openai.py +483 -0
  13. codeframe/agents/__init__.py +8 -0
  14. codeframe/agents/dependency_resolver.py +714 -0
  15. codeframe/auth/__init__.py +16 -0
  16. codeframe/auth/api_key_router.py +238 -0
  17. codeframe/auth/api_keys.py +156 -0
  18. codeframe/auth/dependencies.py +358 -0
  19. codeframe/auth/manager.py +178 -0
  20. codeframe/auth/models.py +30 -0
  21. codeframe/auth/router.py +93 -0
  22. codeframe/auth/schemas.py +15 -0
  23. codeframe/auth/scopes.py +53 -0
  24. codeframe/cli/__init__.py +12 -0
  25. codeframe/cli/__main__.py +20 -0
  26. codeframe/cli/api_client.py +275 -0
  27. codeframe/cli/app.py +5688 -0
  28. codeframe/cli/auth.py +122 -0
  29. codeframe/cli/auth_commands.py +958 -0
  30. codeframe/cli/commands/__init__.py +5 -0
  31. codeframe/cli/config_commands.py +79 -0
  32. codeframe/cli/dashboard_commands.py +67 -0
  33. codeframe/cli/engines_commands.py +205 -0
  34. codeframe/cli/env_commands.py +409 -0
  35. codeframe/cli/helpers.py +56 -0
  36. codeframe/cli/hooks_commands.py +208 -0
  37. codeframe/cli/import_commands.py +129 -0
  38. codeframe/cli/pr_commands.py +549 -0
  39. codeframe/cli/proof_commands.py +415 -0
  40. codeframe/cli/stats_commands.py +311 -0
  41. codeframe/cli/telemetry_runtime.py +153 -0
  42. codeframe/cli/validators.py +123 -0
  43. codeframe/config/rate_limits.py +165 -0
  44. codeframe/core/__init__.py +15 -0
  45. codeframe/core/adapters/__init__.py +43 -0
  46. codeframe/core/adapters/agent_adapter.py +114 -0
  47. codeframe/core/adapters/builtin.py +326 -0
  48. codeframe/core/adapters/claude_code.py +62 -0
  49. codeframe/core/adapters/codex.py +393 -0
  50. codeframe/core/adapters/git_utils.py +40 -0
  51. codeframe/core/adapters/kilocode.py +126 -0
  52. codeframe/core/adapters/opencode.py +48 -0
  53. codeframe/core/adapters/streaming_chat.py +483 -0
  54. codeframe/core/adapters/subprocess_adapter.py +213 -0
  55. codeframe/core/adapters/verification_wrapper.py +269 -0
  56. codeframe/core/agent.py +2183 -0
  57. codeframe/core/agents_config.py +569 -0
  58. codeframe/core/api_key_service.py +211 -0
  59. codeframe/core/artifacts.py +428 -0
  60. codeframe/core/blocker_detection.py +218 -0
  61. codeframe/core/blockers.py +433 -0
  62. codeframe/core/checkpoints.py +481 -0
  63. codeframe/core/conductor.py +2255 -0
  64. codeframe/core/config.py +827 -0
  65. codeframe/core/config_watcher.py +268 -0
  66. codeframe/core/context.py +542 -0
  67. codeframe/core/context_packager.py +234 -0
  68. codeframe/core/credentials.py +735 -0
  69. codeframe/core/dependency_analyzer.py +229 -0
  70. codeframe/core/dependency_graph.py +290 -0
  71. codeframe/core/diagnostic_agent.py +712 -0
  72. codeframe/core/diagnostics.py +616 -0
  73. codeframe/core/editor.py +556 -0
  74. codeframe/core/engine_registry.py +256 -0
  75. codeframe/core/engine_stats.py +231 -0
  76. codeframe/core/environment.py +697 -0
  77. codeframe/core/events.py +375 -0
  78. codeframe/core/executor.py +1005 -0
  79. codeframe/core/fix_tracker.py +480 -0
  80. codeframe/core/gates.py +1322 -0
  81. codeframe/core/git.py +477 -0
  82. codeframe/core/github_connect_service.py +178 -0
  83. codeframe/core/github_integration_config.py +118 -0
  84. codeframe/core/github_issues_service.py +449 -0
  85. codeframe/core/hooks.py +184 -0
  86. codeframe/core/importers/__init__.py +1 -0
  87. codeframe/core/importers/ralph.py +540 -0
  88. codeframe/core/installer.py +650 -0
  89. codeframe/core/models.py +1026 -0
  90. codeframe/core/notifications_config.py +183 -0
  91. codeframe/core/planner.py +437 -0
  92. codeframe/core/prd.py +670 -0
  93. codeframe/core/prd_discovery.py +1118 -0
  94. codeframe/core/prd_stress_test.py +499 -0
  95. codeframe/core/progress.py +126 -0
  96. codeframe/core/proof/__init__.py +34 -0
  97. codeframe/core/proof/capture.py +79 -0
  98. codeframe/core/proof/evidence.py +56 -0
  99. codeframe/core/proof/ledger.py +574 -0
  100. codeframe/core/proof/models.py +162 -0
  101. codeframe/core/proof/obligations.py +103 -0
  102. codeframe/core/proof/runner.py +233 -0
  103. codeframe/core/proof/scope.py +81 -0
  104. codeframe/core/proof/stubs.py +156 -0
  105. codeframe/core/quick_fixes.py +558 -0
  106. codeframe/core/react_agent.py +1650 -0
  107. codeframe/core/reconciliation.py +183 -0
  108. codeframe/core/replay.py +788 -0
  109. codeframe/core/review.py +285 -0
  110. codeframe/core/runtime.py +1134 -0
  111. codeframe/core/sandbox/__init__.py +27 -0
  112. codeframe/core/sandbox/context.py +98 -0
  113. codeframe/core/sandbox/worktree.py +20 -0
  114. codeframe/core/schedule.py +396 -0
  115. codeframe/core/stall_detector.py +71 -0
  116. codeframe/core/stall_monitor.py +134 -0
  117. codeframe/core/state_machine.py +121 -0
  118. codeframe/core/streaming.py +502 -0
  119. codeframe/core/task_tree.py +400 -0
  120. codeframe/core/tasks.py +1022 -0
  121. codeframe/core/telemetry.py +232 -0
  122. codeframe/core/templates.py +221 -0
  123. codeframe/core/tools.py +942 -0
  124. codeframe/core/workspace.py +887 -0
  125. codeframe/core/worktrees.py +276 -0
  126. codeframe/git/__init__.py +5 -0
  127. codeframe/git/github_integration.py +505 -0
  128. codeframe/lib/__init__.py +0 -0
  129. codeframe/lib/audit_logger.py +248 -0
  130. codeframe/lib/metrics_tracker.py +800 -0
  131. codeframe/lib/quality/__init__.py +7 -0
  132. codeframe/lib/quality/complexity_analyzer.py +316 -0
  133. codeframe/lib/quality/owasp_patterns.py +284 -0
  134. codeframe/lib/quality/security_scanner.py +250 -0
  135. codeframe/lib/rate_limiter.py +312 -0
  136. codeframe/notifications/__init__.py +0 -0
  137. codeframe/notifications/webhook.py +380 -0
  138. codeframe/planning/__init__.py +30 -0
  139. codeframe/planning/issue_generator.py +219 -0
  140. codeframe/planning/prd_template_functions.py +137 -0
  141. codeframe/planning/prd_templates.py +975 -0
  142. codeframe/planning/task_scheduler.py +511 -0
  143. codeframe/planning/task_templates.py +533 -0
  144. codeframe/platform_store/__init__.py +5 -0
  145. codeframe/platform_store/database.py +277 -0
  146. codeframe/platform_store/repositories/__init__.py +24 -0
  147. codeframe/platform_store/repositories/api_key_repository.py +245 -0
  148. codeframe/platform_store/repositories/audit_repository.py +67 -0
  149. codeframe/platform_store/repositories/base.py +295 -0
  150. codeframe/platform_store/repositories/interactive_sessions.py +165 -0
  151. codeframe/platform_store/repositories/token_repository.py +598 -0
  152. codeframe/platform_store/repositories/workspace_registry_repository.py +175 -0
  153. codeframe/platform_store/schema_manager.py +321 -0
  154. codeframe/templates/AGENTS.md.default +94 -0
  155. codeframe/tui/__init__.py +5 -0
  156. codeframe/tui/app.py +256 -0
  157. codeframe/tui/data_service.py +103 -0
  158. codeframe/ui/__init__.py +0 -0
  159. codeframe/ui/dependencies.py +103 -0
  160. codeframe/ui/models.py +999 -0
  161. codeframe/ui/response_models.py +201 -0
  162. codeframe/ui/routers/__init__.py +5 -0
  163. codeframe/ui/routers/_helpers.py +29 -0
  164. codeframe/ui/routers/batches_v2.py +315 -0
  165. codeframe/ui/routers/blockers_v2.py +320 -0
  166. codeframe/ui/routers/checkpoints_v2.py +310 -0
  167. codeframe/ui/routers/costs_v2.py +322 -0
  168. codeframe/ui/routers/diagnose_v2.py +225 -0
  169. codeframe/ui/routers/discovery_v2.py +417 -0
  170. codeframe/ui/routers/environment_v2.py +284 -0
  171. codeframe/ui/routers/events_v2.py +75 -0
  172. codeframe/ui/routers/gates_v2.py +166 -0
  173. codeframe/ui/routers/git_v2.py +284 -0
  174. codeframe/ui/routers/github_integrations_v2.py +532 -0
  175. codeframe/ui/routers/interactive_sessions_v2.py +238 -0
  176. codeframe/ui/routers/pr_v2.py +709 -0
  177. codeframe/ui/routers/prd_v2.py +695 -0
  178. codeframe/ui/routers/proof_v2.py +755 -0
  179. codeframe/ui/routers/review_v2.py +360 -0
  180. codeframe/ui/routers/schedule_v2.py +214 -0
  181. codeframe/ui/routers/session_chat_ws.py +354 -0
  182. codeframe/ui/routers/settings_v2.py +562 -0
  183. codeframe/ui/routers/streaming_v2.py +155 -0
  184. codeframe/ui/routers/tasks_v2.py +1098 -0
  185. codeframe/ui/routers/templates_v2.py +232 -0
  186. codeframe/ui/routers/terminal_ws.py +267 -0
  187. codeframe/ui/routers/workspace_v2.py +527 -0
  188. codeframe/ui/server.py +568 -0
  189. codeframe/ui/shared.py +241 -0
  190. codeframe/workspace/__init__.py +5 -0
  191. codeframe/workspace/manager.py +249 -0
  192. codeframe_ai-0.9.0.dist-info/METADATA +517 -0
  193. codeframe_ai-0.9.0.dist-info/RECORD +197 -0
  194. codeframe_ai-0.9.0.dist-info/WHEEL +5 -0
  195. codeframe_ai-0.9.0.dist-info/entry_points.txt +3 -0
  196. codeframe_ai-0.9.0.dist-info/licenses/LICENSE +661 -0
  197. codeframe_ai-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,7 @@
1
+ """Code quality analysis tools."""
2
+
3
+ from codeframe.lib.quality.complexity_analyzer import ComplexityAnalyzer
4
+ from codeframe.lib.quality.security_scanner import SecurityScanner
5
+ from codeframe.lib.quality.owasp_patterns import OWASPPatterns
6
+
7
+ __all__ = ["ComplexityAnalyzer", "SecurityScanner", "OWASPPatterns"]
@@ -0,0 +1,316 @@
1
+ """Complexity analysis using radon.
2
+
3
+ Analyzes code complexity using cyclomatic complexity, Halstead metrics,
4
+ and maintainability index.
5
+ """
6
+
7
+ import logging
8
+ from pathlib import Path
9
+ from typing import List
10
+
11
+ from radon.complexity import cc_visit
12
+ from radon.metrics import mi_visit
13
+
14
+ from codeframe.core.models import ReviewFinding
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ComplexityAnalyzer:
20
+ """Analyzes code complexity using radon.
21
+
22
+ Thresholds (cyclomatic complexity):
23
+ - 1-5: Simple (A) - no finding
24
+ - 6-10: Moderate (B) - medium severity
25
+ - 11-20: Complex (C) - high severity
26
+ - 21-50: Very Complex (D) - high severity
27
+ - 51+: Extremely Complex (F) - critical severity
28
+
29
+ Function length threshold: >50 lines triggers finding
30
+ """
31
+
32
+ # Complexity thresholds
33
+ SIMPLE_THRESHOLD = 5
34
+ MODERATE_THRESHOLD = 10
35
+ COMPLEX_THRESHOLD = 20
36
+ VERY_COMPLEX_THRESHOLD = 50
37
+
38
+ # Function length threshold
39
+ MAX_FUNCTION_LENGTH = 50
40
+
41
+ # Maintainability Index thresholds (0-100, higher is better)
42
+ MI_LOW_THRESHOLD = 20
43
+ MI_MEDIUM_THRESHOLD = 50
44
+
45
+ def __init__(self, project_path: Path):
46
+ """Initialize ComplexityAnalyzer.
47
+
48
+ Args:
49
+ project_path: Path to project root directory
50
+ """
51
+ self.project_path = Path(project_path)
52
+
53
+ def analyze_file(self, file_path: Path) -> List[ReviewFinding]:
54
+ """Analyze a single file for complexity issues.
55
+
56
+ Args:
57
+ file_path: Path to Python file to analyze
58
+
59
+ Returns:
60
+ List of ReviewFinding objects for complexity issues
61
+
62
+ Raises:
63
+ FileNotFoundError: If file doesn't exist
64
+ """
65
+ file_path = Path(file_path)
66
+
67
+ if not file_path.exists():
68
+ raise FileNotFoundError(f"File not found: {file_path}")
69
+
70
+ # Skip non-Python files
71
+ if file_path.suffix != ".py":
72
+ return []
73
+
74
+ # Read file content
75
+ try:
76
+ code = file_path.read_text()
77
+ except Exception as e:
78
+ logger.error(f"Error reading file {file_path}: {e}")
79
+ return []
80
+
81
+ if not code.strip():
82
+ return []
83
+
84
+ findings = []
85
+
86
+ # Analyze cyclomatic complexity
87
+ try:
88
+ complexity_findings = self._analyze_cyclomatic_complexity(file_path, code)
89
+ findings.extend(complexity_findings)
90
+ except Exception as e:
91
+ logger.warning(f"Error analyzing cyclomatic complexity for {file_path}: {e}")
92
+
93
+ # Analyze function length
94
+ try:
95
+ length_findings = self._analyze_function_length(file_path, code)
96
+ findings.extend(length_findings)
97
+ except Exception as e:
98
+ logger.warning(f"Error analyzing function length for {file_path}: {e}")
99
+
100
+ # Analyze maintainability index
101
+ try:
102
+ mi_findings = self._analyze_maintainability_index(file_path, code)
103
+ findings.extend(mi_findings)
104
+ except Exception as e:
105
+ logger.warning(f"Error analyzing maintainability index for {file_path}: {e}")
106
+
107
+ return findings
108
+
109
+ def _analyze_cyclomatic_complexity(self, file_path: Path, code: str) -> List[ReviewFinding]:
110
+ """Analyze cyclomatic complexity using radon.
111
+
112
+ Args:
113
+ file_path: Path to file being analyzed
114
+ code: File content
115
+
116
+ Returns:
117
+ List of findings for high complexity
118
+ """
119
+ findings = []
120
+
121
+ try:
122
+ # Get complexity metrics for all functions/classes
123
+ complexity_blocks = cc_visit(code)
124
+
125
+ for block in complexity_blocks:
126
+ # block attributes: name, lineno, col_offset, endline, complexity, classname
127
+ cc = block.complexity
128
+
129
+ # Determine severity based on complexity
130
+ if cc <= self.SIMPLE_THRESHOLD:
131
+ continue # No finding for simple code
132
+ elif cc <= self.MODERATE_THRESHOLD:
133
+ severity = "medium"
134
+ suggestion = "Consider breaking this function into smaller functions"
135
+ elif cc <= self.COMPLEX_THRESHOLD:
136
+ severity = "high"
137
+ suggestion = (
138
+ "This function is too complex. Break it into smaller, focused functions"
139
+ )
140
+ elif cc <= self.VERY_COMPLEX_THRESHOLD:
141
+ severity = "high"
142
+ suggestion = "URGENT: This function is very complex. Refactor immediately to improve maintainability"
143
+ else:
144
+ severity = "critical"
145
+ suggestion = "CRITICAL: This function is extremely complex. Refactor is required before merging"
146
+
147
+ if cc > self.SIMPLE_THRESHOLD:
148
+ message = f"Cyclomatic complexity {cc} (threshold: {self.SIMPLE_THRESHOLD})"
149
+ if block.classname:
150
+ message = f"{block.classname}.{block.name}: {message}"
151
+ else:
152
+ message = f"{block.name}: {message}"
153
+
154
+ findings.append(
155
+ ReviewFinding(
156
+ category="complexity",
157
+ severity=severity,
158
+ file_path=str(file_path),
159
+ line_number=block.lineno,
160
+ message=message,
161
+ suggestion=suggestion,
162
+ tool="radon",
163
+ )
164
+ )
165
+
166
+ except SyntaxError:
167
+ # File has syntax errors, skip complexity analysis
168
+ logger.debug(f"Syntax error in {file_path}, skipping complexity analysis")
169
+
170
+ return findings
171
+
172
+ def _analyze_function_length(self, file_path: Path, code: str) -> List[ReviewFinding]:
173
+ """Analyze function length.
174
+
175
+ Args:
176
+ file_path: Path to file being analyzed
177
+ code: File content
178
+
179
+ Returns:
180
+ List of findings for overly long functions
181
+ """
182
+ findings = []
183
+
184
+ try:
185
+ complexity_blocks = cc_visit(code)
186
+
187
+ for block in complexity_blocks:
188
+ # Calculate function length
189
+ if hasattr(block, "endline") and hasattr(block, "lineno"):
190
+ length = block.endline - block.lineno + 1
191
+
192
+ if length > self.MAX_FUNCTION_LENGTH:
193
+ severity = "medium" if length < 100 else "high"
194
+
195
+ message = f"Function length {length} lines (threshold: {self.MAX_FUNCTION_LENGTH})"
196
+ if block.classname:
197
+ message = f"{block.classname}.{block.name}: {message}"
198
+ else:
199
+ message = f"{block.name}: {message}"
200
+
201
+ findings.append(
202
+ ReviewFinding(
203
+ category="complexity",
204
+ severity=severity,
205
+ file_path=str(file_path),
206
+ line_number=block.lineno,
207
+ message=message,
208
+ suggestion="Break this long function into smaller, focused functions",
209
+ tool="radon",
210
+ )
211
+ )
212
+
213
+ except SyntaxError:
214
+ logger.debug(f"Syntax error in {file_path}, skipping length analysis")
215
+
216
+ return findings
217
+
218
+ def _analyze_maintainability_index(self, file_path: Path, code: str) -> List[ReviewFinding]:
219
+ """Analyze maintainability index.
220
+
221
+ Args:
222
+ file_path: Path to file being analyzed
223
+ code: File content
224
+
225
+ Returns:
226
+ List of findings for low maintainability
227
+ """
228
+ findings = []
229
+
230
+ try:
231
+ # Get maintainability index (0-100, higher is better)
232
+ mi = mi_visit(code, multi=True)
233
+
234
+ if mi < self.MI_LOW_THRESHOLD:
235
+ severity = "high"
236
+ message = f"Very low maintainability index: {mi:.1f}/100"
237
+ suggestion = "This code is very difficult to maintain. Consider refactoring to improve readability"
238
+ elif mi < self.MI_MEDIUM_THRESHOLD:
239
+ severity = "medium"
240
+ message = f"Low maintainability index: {mi:.1f}/100"
241
+ suggestion = "This code could be more maintainable. Consider refactoring"
242
+ else:
243
+ # Good maintainability, no finding
244
+ return findings
245
+
246
+ findings.append(
247
+ ReviewFinding(
248
+ category="complexity",
249
+ severity=severity,
250
+ file_path=str(file_path),
251
+ line_number=1, # File-level metric
252
+ message=message,
253
+ suggestion=suggestion,
254
+ tool="radon",
255
+ )
256
+ )
257
+
258
+ except Exception as e:
259
+ # MI calculation can fail for various reasons
260
+ logger.debug(f"Could not calculate MI for {file_path}: {e}")
261
+
262
+ return findings
263
+
264
+ def analyze_files(self, file_paths: List[Path]) -> List[ReviewFinding]:
265
+ """Analyze multiple files for complexity issues.
266
+
267
+ Args:
268
+ file_paths: List of file paths to analyze
269
+
270
+ Returns:
271
+ List of all findings from all files
272
+ """
273
+ all_findings = []
274
+
275
+ for file_path in file_paths:
276
+ try:
277
+ findings = self.analyze_file(file_path)
278
+ all_findings.extend(findings)
279
+ except Exception as e:
280
+ logger.error(f"Error analyzing {file_path}: {e}")
281
+
282
+ return all_findings
283
+
284
+ def calculate_score(self, file_paths: List[Path]) -> float:
285
+ """Calculate overall complexity score (0-100, higher is better).
286
+
287
+ Args:
288
+ file_paths: List of file paths to analyze
289
+
290
+ Returns:
291
+ Overall complexity score (0-100)
292
+ """
293
+ if not file_paths:
294
+ return 100.0 # No files = perfect score
295
+
296
+ findings = self.analyze_files(file_paths)
297
+
298
+ # Calculate penalty based on severity
299
+ penalty = 0
300
+ for finding in findings:
301
+ if finding.severity == "critical":
302
+ penalty += 20
303
+ elif finding.severity == "high":
304
+ penalty += 10
305
+ elif finding.severity == "medium":
306
+ penalty += 5
307
+ elif finding.severity == "low":
308
+ penalty += 2
309
+
310
+ # Normalize penalty based on number of files
311
+ penalty_per_file = penalty / len(file_paths)
312
+
313
+ # Calculate score (start at 100, subtract penalties)
314
+ score = max(0, 100 - penalty_per_file)
315
+
316
+ return score
@@ -0,0 +1,284 @@
1
+ """OWASP Top 10 pattern detection.
2
+
3
+ Checks for OWASP A03 (Injection) and A07 (Authentication Failures) patterns.
4
+ """
5
+
6
+ import logging
7
+ import re
8
+ from pathlib import Path
9
+ from typing import List
10
+
11
+ from codeframe.core.models import ReviewFinding
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class OWASPPatterns:
17
+ """Detects OWASP Top 10 security patterns.
18
+
19
+ Currently implements:
20
+ - A03:2021 - Injection (SQL, NoSQL, Command)
21
+ - A07:2021 - Identification and Authentication Failures
22
+ """
23
+
24
+ def __init__(self, project_path: Path):
25
+ """Initialize OWASPPatterns.
26
+
27
+ Args:
28
+ project_path: Path to project root directory
29
+ """
30
+ self.project_path = Path(project_path)
31
+
32
+ # Compile regex patterns for performance
33
+ self._compile_patterns()
34
+
35
+ def _compile_patterns(self):
36
+ """Compile regex patterns for pattern matching."""
37
+ # A03: Injection patterns
38
+ self.sql_concat_pattern = re.compile(
39
+ r'["\']SELECT.*?\+|f["\']SELECT.*?\{|\.format\(.*?SELECT'
40
+ )
41
+ self.sql_fstring_pattern = re.compile(r'f["\'].*?SELECT.*?\{')
42
+ self.nosql_eval_pattern = re.compile(r"\beval\s*\(")
43
+ self.command_injection_pattern = re.compile(r"os\.system\s*\(|subprocess.*shell\s*=\s*True")
44
+
45
+ # A07: Authentication patterns
46
+ self.hardcoded_password_pattern = re.compile(
47
+ r'(password|passwd|pwd|secret|api_key|token)\s*=\s*["\'][^"\']+["\']',
48
+ re.IGNORECASE,
49
+ )
50
+ self.weak_password_pattern = re.compile(r"len\(password\)\s*>=?\s*[1-6]\b")
51
+
52
+ def check_file(self, file_path: Path) -> List[ReviewFinding]:
53
+ """Check a single file for OWASP patterns.
54
+
55
+ Args:
56
+ file_path: Path to Python file to check
57
+
58
+ Returns:
59
+ List of ReviewFinding objects for OWASP violations
60
+
61
+ Raises:
62
+ FileNotFoundError: If file doesn't exist
63
+ """
64
+ file_path = Path(file_path)
65
+
66
+ if not file_path.exists():
67
+ raise FileNotFoundError(f"File not found: {file_path}")
68
+
69
+ # Skip non-Python files
70
+ if file_path.suffix != ".py":
71
+ return []
72
+
73
+ # Read file content
74
+ try:
75
+ code = file_path.read_text()
76
+ except Exception as e:
77
+ logger.error(f"Error reading file {file_path}: {e}")
78
+ return []
79
+
80
+ if not code.strip():
81
+ return []
82
+
83
+ findings = []
84
+
85
+ # Check A03: Injection patterns
86
+ findings.extend(self._check_sql_injection(file_path, code))
87
+ findings.extend(self._check_nosql_injection(file_path, code))
88
+ findings.extend(self._check_command_injection(file_path, code))
89
+
90
+ # Check A07: Authentication failures
91
+ findings.extend(self._check_hardcoded_credentials(file_path, code))
92
+ findings.extend(self._check_weak_password_validation(file_path, code))
93
+
94
+ return findings
95
+
96
+ def _check_sql_injection(self, file_path: Path, code: str) -> List[ReviewFinding]:
97
+ """Check for SQL injection vulnerabilities.
98
+
99
+ Args:
100
+ file_path: Path to file being checked
101
+ code: File content
102
+
103
+ Returns:
104
+ List of findings for SQL injection issues
105
+ """
106
+ findings = []
107
+ lines = code.split("\n")
108
+
109
+ for line_no, line in enumerate(lines, start=1):
110
+ # Check for string concatenation in SQL queries
111
+ if self.sql_concat_pattern.search(line) or self.sql_fstring_pattern.search(line):
112
+ # Make sure it's actually a SQL query
113
+ if any(
114
+ keyword in line.upper()
115
+ for keyword in ["SELECT", "INSERT", "UPDATE", "DELETE", "FROM", "WHERE"]
116
+ ):
117
+ findings.append(
118
+ ReviewFinding(
119
+ category="security",
120
+ severity="critical",
121
+ file_path=str(file_path),
122
+ line_number=line_no,
123
+ message="[A03] Potential SQL injection vulnerability detected",
124
+ suggestion="Use parameterized queries or an ORM to prevent SQL injection",
125
+ tool="owasp",
126
+ )
127
+ )
128
+
129
+ return findings
130
+
131
+ def _check_nosql_injection(self, file_path: Path, code: str) -> List[ReviewFinding]:
132
+ """Check for NoSQL injection vulnerabilities.
133
+
134
+ Args:
135
+ file_path: Path to file being checked
136
+ code: File content
137
+
138
+ Returns:
139
+ List of findings for NoSQL injection issues
140
+ """
141
+ findings = []
142
+ lines = code.split("\n")
143
+
144
+ for line_no, line in enumerate(lines, start=1):
145
+ # Check for eval() usage (extremely dangerous)
146
+ if self.nosql_eval_pattern.search(line):
147
+ findings.append(
148
+ ReviewFinding(
149
+ category="security",
150
+ severity="critical",
151
+ file_path=str(file_path),
152
+ line_number=line_no,
153
+ message="[A03] eval() usage detected - extremely dangerous",
154
+ suggestion="Never use eval() with user input. Use ast.literal_eval() or JSON parsing instead",
155
+ tool="owasp",
156
+ )
157
+ )
158
+
159
+ return findings
160
+
161
+ def _check_command_injection(self, file_path: Path, code: str) -> List[ReviewFinding]:
162
+ """Check for command injection vulnerabilities.
163
+
164
+ Args:
165
+ file_path: Path to file being checked
166
+ code: File content
167
+
168
+ Returns:
169
+ List of findings for command injection issues
170
+ """
171
+ findings = []
172
+ lines = code.split("\n")
173
+
174
+ for line_no, line in enumerate(lines, start=1):
175
+ # Check for os.system() or subprocess with shell=True
176
+ if self.command_injection_pattern.search(line):
177
+ findings.append(
178
+ ReviewFinding(
179
+ category="security",
180
+ severity="high",
181
+ file_path=str(file_path),
182
+ line_number=line_no,
183
+ message="[A03] Potential command injection vulnerability",
184
+ suggestion="Use subprocess with shell=False and validate all inputs. Avoid os.system()",
185
+ tool="owasp",
186
+ )
187
+ )
188
+
189
+ return findings
190
+
191
+ def _check_hardcoded_credentials(self, file_path: Path, code: str) -> List[ReviewFinding]:
192
+ """Check for hardcoded credentials.
193
+
194
+ Args:
195
+ file_path: Path to file being checked
196
+ code: File content
197
+
198
+ Returns:
199
+ List of findings for hardcoded credentials
200
+ """
201
+ findings = []
202
+ lines = code.split("\n")
203
+
204
+ for line_no, line in enumerate(lines, start=1):
205
+ # Skip comments
206
+ if line.strip().startswith("#"):
207
+ continue
208
+
209
+ # Check for hardcoded passwords/secrets
210
+ match = self.hardcoded_password_pattern.search(line)
211
+ if match:
212
+ # Filter out common false positives
213
+ if any(
214
+ fp in line.lower() for fp in ["test", "example", "dummy", "mock", "placeholder"]
215
+ ):
216
+ continue
217
+
218
+ # Filter out empty strings
219
+ if '""' in line or "''" in line:
220
+ continue
221
+
222
+ findings.append(
223
+ ReviewFinding(
224
+ category="security",
225
+ severity="high",
226
+ file_path=str(file_path),
227
+ line_number=line_no,
228
+ message="[A07] Hardcoded credentials detected",
229
+ suggestion="Use environment variables or a secrets manager to store sensitive data",
230
+ tool="owasp",
231
+ )
232
+ )
233
+
234
+ return findings
235
+
236
+ def _check_weak_password_validation(self, file_path: Path, code: str) -> List[ReviewFinding]:
237
+ """Check for weak password validation.
238
+
239
+ Args:
240
+ file_path: Path to file being checked
241
+ code: File content
242
+
243
+ Returns:
244
+ List of findings for weak password validation
245
+ """
246
+ findings = []
247
+ lines = code.split("\n")
248
+
249
+ for line_no, line in enumerate(lines, start=1):
250
+ # Check for weak password length validation
251
+ if self.weak_password_pattern.search(line):
252
+ findings.append(
253
+ ReviewFinding(
254
+ category="security",
255
+ severity="medium",
256
+ file_path=str(file_path),
257
+ line_number=line_no,
258
+ message="[A07] Weak password validation detected",
259
+ suggestion="Require passwords to be at least 8 characters with complexity requirements",
260
+ tool="owasp",
261
+ )
262
+ )
263
+
264
+ return findings
265
+
266
+ def check_files(self, file_paths: List[Path]) -> List[ReviewFinding]:
267
+ """Check multiple files for OWASP patterns.
268
+
269
+ Args:
270
+ file_paths: List of file paths to check
271
+
272
+ Returns:
273
+ List of all findings from all files
274
+ """
275
+ all_findings = []
276
+
277
+ for file_path in file_paths:
278
+ try:
279
+ findings = self.check_file(file_path)
280
+ all_findings.extend(findings)
281
+ except Exception as e:
282
+ logger.error(f"Error checking {file_path}: {e}")
283
+
284
+ return all_findings