Coverage for src/alprina_cli/fix_command.py: 16%
116 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
1"""
2Fix command - AI-powered vulnerability remediation.
3Generates and applies secure code fixes for vulnerabilities.
4"""
6from pathlib import Path
7from typing import Optional, Dict, List
8from rich.console import Console
9from rich.panel import Panel
10from rich.table import Table
11from rich.syntax import Syntax
12from rich.prompt import Confirm
13import json
15from .services.fix_generator import get_fix_generator
16from loguru import logger
18console = Console()
21def fix_command(
22 target: str,
23 finding_id: Optional[str] = None,
24 auto_fix: bool = False,
25 severity: Optional[str] = None,
26 preview_only: bool = False
27):
28 """
29 Generate AI-powered fixes for vulnerabilities.
31 Args:
32 target: Path to scan or specific file
33 finding_id: Specific finding ID to fix
34 auto_fix: Automatically apply fixes without confirmation
35 severity: Fix only specific severity (critical, high, medium, low)
36 preview_only: Show fixes without applying
37 """
38 console.print(Panel(
39 f"🤖 AI-Powered Fix Generator\n\n"
40 f"Target: [bold]{target}[/bold]\n"
41 f"Mode: {'[green]Auto-apply[/green]' if auto_fix else '[yellow]Interactive[/yellow]'}",
42 title="Alprina Fix",
43 border_style="cyan"
44 ))
46 try:
47 # TODO: Integrate with existing scan results
48 # For now, demonstrate with sample finding
49 _demo_fix_generation(target, auto_fix, preview_only)
51 except Exception as e:
52 console.print(f"[red]Fix generation failed: {e}[/red]")
53 logger.error(f"Fix command error: {e}", exc_info=True)
56def suggest_fixes_command(scan_results_file: str):
57 """
58 Suggest fixes for findings in a scan results file.
60 Args:
61 scan_results_file: Path to JSON file with scan results
62 """
63 try:
64 # Load scan results
65 with open(scan_results_file, 'r') as f:
66 results = json.load(f)
68 findings = results.get("findings", [])
70 if not findings:
71 console.print("[yellow]No findings in scan results[/yellow]")
72 return
74 console.print(Panel(
75 f"📋 Analyzing {len(findings)} findings\n"
76 f"Generating AI-powered fix suggestions...",
77 title="Fix Suggestions",
78 border_style="cyan"
79 ))
81 # Generate fixes for all findings
82 fix_generator = get_fix_generator()
84 for i, finding in enumerate(findings, 1):
85 console.print(f"\n[bold cyan]Finding {i}/{len(findings)}:[/bold cyan]")
86 _process_single_finding(finding, fix_generator, preview_only=True)
88 except FileNotFoundError:
89 console.print(f"[red]Scan results file not found: {scan_results_file}[/red]")
90 except json.JSONDecodeError:
91 console.print(f"[red]Invalid JSON in scan results file[/red]")
92 except Exception as e:
93 console.print(f"[red]Error: {e}[/red]")
94 logger.error(f"Suggest fixes error: {e}", exc_info=True)
97def _demo_fix_generation(target: str, auto_fix: bool, preview_only: bool):
98 """
99 Demo fix generation with sample vulnerability.
100 TODO: Replace with real scan integration.
101 """
102 # Sample vulnerability for demonstration
103 sample_finding = {
104 "type": "SQL Injection",
105 "severity": "CRITICAL",
106 "description": "User input directly concatenated into SQL query without sanitization",
107 "location": f"{target}:45",
108 "line": 45,
109 "cwe": "CWE-89",
110 "cvss_score": 9.8
111 }
113 # Sample vulnerable code
114 sample_code = '''import sqlite3
116def get_user_by_id(user_id):
117 """Get user from database by ID."""
118 conn = sqlite3.connect('users.db')
119 cursor = conn.cursor()
121 # VULNERABLE: Direct string formatting
122 query = f"SELECT * FROM users WHERE id = {user_id}"
124 cursor.execute(query)
125 result = cursor.fetchone()
126 conn.close()
127 return result
128'''
130 fix_generator = get_fix_generator()
132 console.print("\n[bold]🔍 Analyzing Vulnerability...[/bold]")
133 console.print(f" Type: [red]{sample_finding['type']}[/red]")
134 console.print(f" Severity: [red]{sample_finding['severity']}[/red]")
135 console.print(f" Location: [cyan]{sample_finding['location']}[/cyan]")
136 if sample_finding.get("cwe"):
137 console.print(f" CWE: [cyan]{sample_finding['cwe']}[/cyan]")
138 if sample_finding.get("cvss_score"):
139 console.print(f" CVSS: [red]{sample_finding['cvss_score']}/10.0[/red]")
141 console.print("\n[bold]🤖 Generating AI-Powered Fix...[/bold]")
143 # Generate fix
144 fix_data = fix_generator.generate_fix(
145 code=sample_code,
146 vulnerability=sample_finding,
147 filename=target
148 )
150 # Display results
151 _display_fix(sample_code, fix_data, auto_fix, preview_only)
154def _process_single_finding(finding: Dict, fix_generator, preview_only: bool = True):
155 """Process a single finding and generate fix."""
156 console.print(f" Type: [yellow]{finding.get('type')}[/yellow]")
157 console.print(f" Severity: [red]{finding.get('severity')}[/red]")
158 console.print(f" Location: [cyan]{finding.get('location')}[/cyan]")
160 # TODO: Load actual file content
161 # For now, skip file reading
162 console.print(" [dim]Fix generation requires file access (not implemented in this demo)[/dim]")
165def _display_fix(original_code: str, fix_data: dict, auto_fix: bool, preview_only: bool):
166 """Display fix with before/after comparison."""
168 if fix_data.get("error"):
169 console.print(f"\n[red]❌ Error generating fix:[/red] {fix_data['error']}")
170 return
172 confidence = fix_data.get("confidence", 0.0)
174 # Show confidence score
175 confidence_color = "green" if confidence >= 0.8 else "yellow" if confidence >= 0.6 else "red"
176 console.print(f"\n[bold]✨ Fix Generated (Confidence: [{confidence_color}]{confidence*100:.0f}%[/{confidence_color}])[/bold]")
178 # Show explanation
179 if fix_data.get("explanation"):
180 console.print(f"\n[bold cyan]💡 Why This Fix Works:[/bold cyan]")
181 console.print(Panel(
182 fix_data["explanation"],
183 border_style="cyan",
184 padding=(1, 2)
185 ))
187 # Show changes
188 if fix_data.get("changes"):
189 console.print(f"\n[bold cyan]📝 Changes Made:[/bold cyan]")
190 for i, change in enumerate(fix_data["changes"], 1):
191 console.print(f" {i}. {change}")
193 # Show before/after code
194 console.print(f"\n[bold red]❌ Before (Vulnerable):[/bold red]")
195 syntax = Syntax(original_code.strip(), "python", theme="monokai", line_numbers=True)
196 console.print(Panel(syntax, border_style="red"))
198 fixed_code = fix_data.get("fixed_code", "")
199 if fixed_code:
200 console.print(f"\n[bold green]✅ After (Secure):[/bold green]")
201 syntax = Syntax(fixed_code.strip(), "python", theme="monokai", line_numbers=True)
202 console.print(Panel(syntax, border_style="green"))
204 # Show diff
205 if fix_data.get("diff"):
206 console.print(f"\n[bold cyan]🔄 Diff:[/bold cyan]")
207 console.print(Panel(fix_data["diff"], border_style="cyan"))
209 # Show security notes
210 if fix_data.get("security_notes"):
211 console.print(f"\n[bold yellow]⚠️ Security Notes:[/bold yellow]")
212 for i, note in enumerate(fix_data["security_notes"], 1):
213 console.print(f" {i}. {note}")
215 # Apply fix (interactive or auto)
216 if not preview_only:
217 _apply_fix_interactive(fix_data, auto_fix)
220def _apply_fix_interactive(fix_data: dict, auto_fix: bool):
221 """Interactively apply fix with user confirmation."""
223 if auto_fix:
224 console.print("\n[green]✓[/green] Auto-applying fix...")
225 # TODO: Actually apply the fix
226 console.print("[green]✓ Fix applied![/green]")
227 console.print("[dim]💾 Backup saved as: file.py.backup[/dim]")
228 else:
229 console.print("\n[bold]Apply this fix?[/bold]")
230 console.print(" [y] Yes, apply the fix")
231 console.print(" [n] No, skip this fix")
232 console.print(" [d] Show diff again")
233 console.print(" [e] Explain in detail")
235 if Confirm.ask("Apply fix?", default=False):
236 # TODO: Actually apply the fix
237 console.print("[green]✓ Fix applied![/green]")
238 console.print("[dim]💾 Backup saved as: file.py.backup[/dim]")
239 else:
240 console.print("[yellow]⊗ Fix skipped[/yellow]")
243def _show_fix_summary(total: int, applied: int, skipped: int):
244 """Show summary of fix session."""
245 console.print("\n" + "="*60)
246 console.print("[bold]📊 Fix Summary[/bold]")
247 console.print("="*60)
249 table = Table(show_header=False, box=None)
250 table.add_row("Total findings:", str(total))
251 table.add_row("Fixes applied:", f"[green]{applied}[/green]")
252 table.add_row("Fixes skipped:", f"[yellow]{skipped}[/yellow]")
254 console.print(table)
256 if applied > 0:
257 console.print("\n[green]✓[/green] Run your tests to verify the fixes work correctly")
258 console.print("[green]✓[/green] Backups saved with .backup extension")
260 console.print("\n[dim]Tip: Run 'alprina scan' again to verify fixes resolved the issues[/dim]")