diff --git a/cli_isolated/vulnerability_checker.py b/cli_isolated/vulnerability_checker.py index d96f9e0..7808db8 100644 --- a/cli_isolated/vulnerability_checker.py +++ b/cli_isolated/vulnerability_checker.py @@ -1,7 +1,8 @@ from pathlib import Path from typing import Tuple -from safety_schemas.models import FileModel, Vulnerability, VulnerabilitySeverityLabels +from safety_schemas.models import (FileModel, Vulnerability, + VulnerabilitySeverityLabels) EXIT_CODE_VULNERABILITIES_FOUND = 64 @@ -25,8 +26,13 @@ def pluralize(word: str, count: int = 0) -> str: if word in default: return default[word] - if word.endswith("s") or word.endswith("x") or word.endswith("z") \ - or word.endswith("ch") or word.endswith("sh"): + if ( + word.endswith("s") + or word.endswith("x") + or word.endswith("z") + or word.endswith("ch") + or word.endswith("sh") + ): return word + "es" if word.endswith("y"): @@ -37,6 +43,7 @@ def pluralize(word: str, count: int = 0) -> str: return word + "s" + def check_vulnerabilities( file_path: Path, inspectable_file, @@ -54,12 +61,13 @@ def check_vulnerabilities( exit_code = 0 count_vulns_scanned = 0 affected_count = 0 + fixes_count = 0 dependency_vuln_detected = False + total_resolved_vulns = 0 count_vulns_scanned += len(inspectable_file.dependency_results.dependencies) if exit_code == 0 and inspectable_file.dependency_results.failed: - exit_code = EXIT_CODE_VULNERABILITIES_FOUND - + exit_code = EXIT_CODE_VULNERABILITIES_FOUND dependency_results = inspectable_file.dependency_results affected_specifications = dependency_results.get_affected_specifications() @@ -80,7 +88,9 @@ def check_vulnerabilities( # Sort vulnerabilities by severity vulns_to_report = sorted( [v for v in spec.vulnerabilities if not v.ignored], - key=lambda v: v.severity.cvssv3.get("base_score", 0) if v.severity and v.severity.cvssv3 else 0, + key=lambda v: v.severity.cvssv3.get("base_score", 0) + if v.severity and v.severity.cvssv3 + else 0, reverse=True, ) critical_vulns_count = sum( @@ -102,8 +112,49 @@ def check_vulnerabilities( output.append(msg) + remediation_lines = [] + if spec.remediation.recommended: + total_resolved_vulns += spec.remediation.vulnerabilities_found + + # Put remediation here + if not spec.remediation.recommended: + remediation_lines.append( + f"No known fix for [dep_name]{spec.name}[/dep_name][specifier]{spec.raw.replace(spec.name, '')}[/specifier] to fix " + f"[number]{spec.remediation.vulnerabilities_found}[/number] " + f"{vuln_word}" + ) + else: + msg = ( + f"[rem_brief]Update {spec.raw} to " + f"{spec.name}=={spec.remediation.recommended}[/rem_brief] to fix " + f"[number]{spec.remediation.vulnerabilities_found}[/number] " + f"{vuln_word}" + ) + if ( + spec.remediation.vulnerabilities_found > 3 + and critical_vulns_count > 0 + ): + msg += f", [rem_severity]including {critical_vulns_count} critical severity {pluralize('vulnerability', critical_vulns_count)}[/rem_severity] :stop_sign:" + + fixes_count += 1 + remediation_lines.append(f"{msg}") + if spec.remediation.other_recommended: + other = "[/recommended_ver], [recommended_ver]".join( + spec.remediation.other_recommended + ) + remediation_lines.append( + f"Versions of {spec.name} with no known vulnerabilities: " + f"[recommended_ver]{other}[/recommended_ver]" + ) + + for line in remediation_lines: + output.append(line) + output.append(f"Learn more: [link]{spec.remediation.more_info_url}[/link]") + else: - output.append(f"No vulnerabilities found in {file_path}.") + output.append( + f":white_check_mark: [file_title]{file_path}: No issues found.[/file_title]" + ) # Prepare and return the FileModel file_model = FileModel(