diff --git a/.gitignore b/.gitignore index 176131f2..ed2b1db2 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ venv/ # Mac OS .DS_Store +.safety-project.ini diff --git a/safety/scan/command.py b/safety/scan/command.py index 4bbba379..744b2092 100644 --- a/safety/scan/command.py +++ b/safety/scan/command.py @@ -27,7 +27,7 @@ SYSTEM_SCAN_TARGET_HELP, SCAN_APPLY_FIXES, SCAN_DETAILED_OUTPUT, CLI_SCAN_COMMAND_HELP, CLI_SYSTEM_SCAN_COMMAND_HELP from safety.scan.decorators import inject_metadata, scan_project_command_init, scan_system_command_init from safety.scan.finder.file_finder import should_exclude -from safety.scan.main import load_policy_file, load_unverified_project_from_config, process_files, save_report_as +from safety.scan.main import get_report, load_policy_file, load_unverified_project_from_config, process_files, save_report_as from safety.scan.models import ScanExport, ScanOutput, SystemScanExport, SystemScanOutput from safety.scan.render import print_detected_ecosystems_section, print_fixes_section, print_summary, render_scan_html, render_scan_spdx, render_to_console from safety.scan.util import Stage @@ -98,6 +98,25 @@ scan_system_app = typer.Typer(**cli_apps_opts) +def safe_get(data: Dict, keys: List[str], default: Any = None) -> Any: + """ + Safely retrieve a value from a nested dictionary using a list of keys. + + Args: + data (Dict): The dictionary to retrieve the value from. + keys (List[str]): List of keys representing the path to the desired value. + default (Any): The default value to return if any key is not found. + + Returns: + Any: The value at the specified path, or the default value if the path doesn't exist. + """ + for key in keys: + if not isinstance(data, dict): + return default + data = data.get(key, default) + return data + + class ScannableEcosystems(Enum): """Enum representing scannable ecosystems.""" PYTHON = Ecosystem.PYTHON.value @@ -106,7 +125,7 @@ class ScannableEcosystems(Enum): def process_report( obj: Any, console: Console, report: ReportModel, output: str, save_as: Optional[Tuple[str, Path]], detailed_output: bool = False, - filter_keys: Optional[List[str]] = None, + filter_keys: Optional[List[str]] = None, use_server_matching: bool = False, **kwargs ) -> Optional[str]: """ @@ -183,7 +202,7 @@ def process_report( report_to_export) report_url = None - if obj.platform_enabled: + if obj.platform_enabled and not use_server_matching: status.update(f"{ICON_UPLOAD} {MSG_UPLOADING_REPORT.format(SAFETY_PLATFORM_URL)}") try: result = obj.auth.client.upload_report(json_format) @@ -646,6 +665,218 @@ def process_file_fixes(file_to_fix: FileModel, return process_fixes_scan(file_to_fix, specs_to_fix, update_limits, output, no_output=no_output, prompt=prompt) +def get_affected_specifications_from_json(dependencies: List[Dict], include_ignored: bool = False) -> List[Dict]: + """ + Mimics the behavior of `get_affected_specifications` for JSON-based data. + + Args: + dependencies (List[Dict]): List of dependencies from the JSON response. + include_ignored (bool): Whether to include ignored vulnerabilities. + + Returns: + List[Dict]: List of affected specifications (as dictionaries). + """ + affected = [] + for dep in dependencies: + for spec in dep.get("specifications", []): + vulnerabilities = safe_get(spec, ["vulnerabilities", "known_vulnerabilities"], []) + if include_ignored: + has_vulnerabilities = bool(vulnerabilities) + else: + # Filter out ignored vulnerabilities + has_vulnerabilities = any( + not safe_get(vuln, ["ignored", "code"]) for vuln in vulnerabilities + ) + + if has_vulnerabilities: + affected.append(spec) + return affected + + +def sort_vulns_by_score_json(vuln: Dict) -> int: + """ + Sort vulnerabilities from JSON data by CVSSv3 base score. + + Args: + vuln (Dict): A dictionary representing a vulnerability. + + Returns: + int: The CVSSv3 base score, or 0 if not present. + """ + cvssv3 = safe_get(vuln, ["cve", "cvssv3"], {}) + return cvssv3.get("base_score", 0) if cvssv3 else 0 + + +def sort_and_filter_vulnerabilities_json( + vulnerabilities: List[Dict[str, Any]], key_func: Callable[[Dict[str, Any]], int], reverse: bool = True +) -> List[Dict[str, Any]]: + """ + Sorts and filters vulnerabilities from a JSON structure. + + Args: + vulnerabilities (List[Dict[str, Any]]): A list of vulnerability dictionaries to sort and filter. + key_func (Callable[[Dict[str, Any]], int]): A function to determine the sort key. + reverse (bool): Whether to sort in descending order (default is True). + + Returns: + List[Dict[str, Any]]: The sorted and filtered list of vulnerabilities. + """ + return sorted( + [ + vuln + for vuln in vulnerabilities + if not (safe_get(vuln, ["ignored", "reason"]) or safe_get(vuln, ["ignored", "expires"])) + ], + key=key_func, + reverse=reverse, + ) + + +def count_critical_vulnerabilities_json(vulnerabilities: List[Dict[str, Any]]) -> int: + """ + Count the number of critical vulnerabilities in a list of JSON vulnerabilities. + + Args: + vulnerabilities (List[Dict[str, Any]]): List of vulnerabilities represented as dictionaries. + + Returns: + int: The number of vulnerabilities with a critical severity level. + """ + return sum( + 1 for vuln in vulnerabilities + if safe_get(vuln, ["cve", "cvssv3", "base_severity"], "").lower() == "critical" + ) + + +def render_vulnerabilities_json(vulns_to_report: List[Dict], console: Console, detailed_output: bool) -> None: + """ + Render vulnerabilities from JSON data to the console. + + Args: + vulns_to_report (List[Dict]): List of vulnerabilities from JSON to render. + console (Console): Console object for printing. + detailed_output (bool): Whether to display detailed output. + """ + for vuln in vulns_to_report: + render_to_console_json( + vuln, console, rich_kwargs=RICH_DEFAULT_KWARGS, detailed_output=detailed_output + ) + + +def render_to_console_json(vuln: Dict, console: Console, rich_kwargs: Dict, detailed_output: bool) -> None: + """ + Render a single vulnerability from JSON to the console. + + Args: + vuln (Dict): A vulnerability dictionary from JSON data. + console (Console): Console object for printing. + rich_kwargs (Dict): Additional arguments for rich text formatting. + detailed_output (bool): Whether to display detailed output. + """ + # Extract details from the vulnerability + vuln_id = vuln.get("id", "Unknown ID") + advisory = vuln.get("advisory", "No advisory available") + cve = safe_get(vuln, ["cve", "name"], "N/A") + severity = safe_get(vuln, ["cve", "cvssv3", "base_severity"], "N/A") + base_score = safe_get(vuln, ["cve", "cvssv3", "base_score"], "N/A") + vector = safe_get(vuln, ["cve", "cvssv3", "vector_string"], "N/A") + ignored = safe_get(vuln, ["ignored", "reason"], None) + + # Format the vulnerability information + msg = f"[{severity}] {vuln_id}: {advisory} (CVSS Base Score: {base_score})" + if cve != "N/A": + msg += f" | CVE: {cve}" + if vector != "N/A": + msg += f" | Vector: {vector}" + + # Display the message + console.print(msg, **rich_kwargs) + + # Show ignored reason if available + if ignored: + console.print(f"Ignored: {ignored}", style="dim") + + # If detailed output is requested, include full advisory details + if detailed_output: + console.print(advisory, style="italic") + + +def generate_remediation_details_json(spec: Dict, spec_name:str, vuln_word: str, critical_vulns_count: int) -> Tuple[List[str], int, int]: + """ + Generate remediation details for a specific dependency from JSON data. + + Args: + spec (Dict): A dictionary representing a dependency specification. + vuln_word (str): Pluralized word for vulnerabilities. + critical_vulns_count (int): Number of critical vulnerabilities. + + Returns: + Tuple[List[str], int, int]: A tuple containing: + - List of remediation lines. + - Total resolved vulnerabilities. + - Fixes count. + """ + lines = [] + total_resolved_vulns = 0 + fixes_count = 0 + + remediation = safe_get(spec, ["vulnerabilities", "remediation"], {}) + vulnerabilities_found = remediation.get("vulnerabilities_found", 0) + recommended = remediation.get("recommended") + other_recommended = remediation.get("other_recommended", []) + spec_raw = spec.get("raw", "unknown") + + if not recommended: + lines.append( + MSG_NO_KNOWN_FIX.format(spec_name, spec_raw.replace(spec_name, ''), vulnerabilities_found, vuln_word) + ) + else: + total_resolved_vulns += vulnerabilities_found + msg = MSG_RECOMMENDED_UPDATE.format(spec_raw, spec_name, recommended, vulnerabilities_found, vuln_word) + + if vulnerabilities_found > CRITICAL_VULN_THRESHOLD and critical_vulns_count > MIN_CRITICAL_COUNT: + msg += f", {TAG_REM_SEVERITY}including {critical_vulns_count} critical severity {pluralize('vulnerability', critical_vulns_count)}{TAG_REM_SEVERITY} {ICON_STOP_SIGN}" + + fixes_count += 1 + lines.append(msg) + + if other_recommended: + other_versions = "[/recommended_ver], [recommended_ver]".join(other_recommended) + lines.append(MSG_NO_VULNERABILITIES.format(spec_name, other_versions)) + + return lines, total_resolved_vulns, fixes_count + + +def should_display_fix_suggestion_json( + ctx: typer.Context, + file_data: Dict, + affected_specifications: List[Dict], + apply_updates: bool +) -> bool: + """ + Determine whether to display a fix suggestion based on the current context and file analysis (JSON-based). + + Args: + ctx (typer.Context): The Typer context object. + file_data (Dict): The JSON data for the file being analyzed. + affected_specifications (List[Dict]): List of affected specifications (from JSON). + apply_updates (bool): Whether fixes are being applied. + + Returns: + bool: True if the fix suggestion should be displayed, False otherwise. + """ + ecosystem = file_data.get("categories", []) + file_type = file_data.get("type") + + return ( + ctx.obj.auth.stage == Stage.development + and "python" in ecosystem + and file_type == "requirements.txt" + and any(affected_specifications) + and not apply_updates + ) + + @scan_project_app.command( cls=SafetyCLICommand, help=CLI_SCAN_COMMAND_HELP, @@ -710,7 +941,7 @@ def scan(ctx: typer.Context, bool, typer.Option( "--use-server-matching", - help="Flag to enable using server side vulnerability matching. This just sends data to server for now.", + help="Flag to enable using server side vulnerability matching.", show_default=False, ), ] = False, @@ -762,83 +993,186 @@ def scan(ctx: typer.Context, # Process each file for dependencies and vulnerabilities with console.status(wait_msg, spinner=DEFAULT_SPINNER): - for path, analyzed_file in process_files(paths=file_paths, - config=config, use_server_matching=use_server_matching, obj=ctx.obj, target=target): - - # Update counts and track vulnerabilities - count += len(analyzed_file.dependency_results.dependencies) - if exit_code == 0 and analyzed_file.dependency_results.failed: - exit_code = EXIT_CODE_VULNERABILITIES_FOUND - - affected_specifications = analyzed_file.dependency_results.get_affected_specifications() - affected_count += len(affected_specifications) - # Sort vulnerabilities by severity - def sort_vulns_by_score(vuln: Vulnerability) -> int: - if vuln.severity and vuln.severity.cvssv3: - return vuln.severity.cvssv3.get("base_score", 0) - return 0 - - # Prepare to collect files needing fixes - to_fix_spec = [] - file_matched_for_fix = analyzed_file.file_type.value in fix_file_types - - # Handle files with affected specifications - if any(affected_specifications): - dependency_vuln_detected = detect_dependency_vulnerabilities(console, dependency_vuln_detected) - print_file_info(console, path, target) + if use_server_matching: + response_json = get_report(paths=file_paths, config=config, use_server_matching=True, obj=ctx.obj, target=target) + projects = safe_get(response_json, ["scan_results", "projects"], []) + for project in projects: + project_files = project.get("files", []) + + for file_data in project_files: + file_location = Path(file_data.get("location", "unknown")).resolve() + target_path = target.resolve() + file_type = file_data.get("type") + file_results = file_data.get("results", {}) + dependencies = file_results.get("dependencies", []) + + count += len(dependencies) + affected_specifications = get_affected_specifications_from_json(dependencies) + affected_count += len(affected_specifications) + to_fix_spec = [] + file_matched_for_fix = file_data.get("type") in fix_file_types for spec in affected_specifications: - if file_matched_for_fix: - to_fix_spec.append(spec) - - # Print vulnerabilities for each specification + if spec["vulnerabilities"]: + if len(spec["vulnerabilities"]["known_vulnerabilities"]) < MIN_DETAILED_OUTPUT_THRESHOLD: + detailed_output = True + break + + + # Handle files with affected specifications + if any(affected_specifications): + dependency_vuln_detected = detect_dependency_vulnerabilities(console, dependency_vuln_detected) + print_file_info(console, Path(file_location), target) + + for spec in affected_specifications: + if file_matched_for_fix: + to_fix_spec.append(spec) + + # Print vulnerabilities for each specification + console.print() + for dependency in dependencies: + + + for specification in dependency.get("specifications", []): + vulns_to_report = sort_and_filter_vulnerabilities_json( + safe_get(specification, ["vulnerabilities", "known_vulnerabilities"], []), + key_func=sort_vulns_by_score_json, + ) + critical_vulns_count = count_critical_vulnerabilities_json(vulns_to_report) + vulns_found = len(vulns_to_report) + vuln_word = pluralize("vulnerability", vulns_found) + spec_name = dependency.get("name").lower() + spec_raw = specification.get("raw").lower() + + + if (spec_name in spec_raw): + if vulns_found != 0: + msg = generate_vulnerability_message(spec_name, spec_raw, vulns_found, critical_vulns_count, vuln_word) + console.print(Padding(f"{msg}]", PADDING_VALUES), emoji=True, overflow="crop") + lines, resolved_vulns, fixes = generate_remediation_details_json(spec, spec_name, vuln_word, critical_vulns_count) + total_resolved_vulns += resolved_vulns + fixes_count += fixes + + for line in lines: + console.print(Padding(line, PADDING_VALUES), emoji=True) + + # construct the url HERE + # https://data.safetycli.com/p/pypi/django/eda/?from=2.2&to=4.2.17 + # Construct this with 3 inputs- django, raw, + remediation = safe_get(spec, ["vulnerabilities", "remediation"], {}) + recommended = remediation.get("recommended") + URL_PREFIX = "https://data.safetycli.com/p/pypi/" + URL_MIDDLE = "/eda/?from=" + URL_END = "&to=" + more_info_url = URL_PREFIX+spec_name+URL_MIDDLE+spec_raw.split("==")[1]+URL_END+recommended + + if more_info_url: + console.print( + Padding(MSG_LEARN_MORE.format(more_info_url), PADDING_VALUES), emoji=True + ) + + # # Track whether to suggest applying fixes + # display_apply_fix_suggestion = should_display_fix_suggestion_json( + # ctx, file_data, affected_specifications, apply_updates + # ) + if detailed_output: + render_vulnerabilities_json(vulns_to_report, console, detailed_output) + + else: + # Handle files with no issues console.print() - vulns_to_report = sort_and_filter_vulnerabilities(spec.vulnerabilities, key_func=sort_vulns_by_score) - critical_vulns_count = count_critical_vulnerabilities(vulns_to_report) - vulns_found = len(vulns_to_report) - vuln_word = pluralize("vulnerability", vulns_found) - - msg = generate_vulnerability_message(spec.name, spec.raw, vulns_found, critical_vulns_count, vuln_word) - console.print(Padding(f"{msg}]", PADDING_VALUES), emoji=True, overflow="crop") + file_location = Path(file_data.get("location", "unknown")).resolve() + console.print( + f"{ICON_CHECKMARK} [file_title]{file_location.relative_to(target)}: No issues found.[/file_title]", + emoji=True + ) - # Display detailed vulnerability information if applicable - if detailed_output or vulns_found < MIN_DETAILED_OUTPUT_THRESHOLD: - render_vulnerabilities(vulns_to_report, console, detailed_output) + - # Generate remediation details and print them - lines, resolved_vulns, fixes = generate_remediation_details(spec, vuln_word, critical_vulns_count) - total_resolved_vulns += resolved_vulns - fixes_count += fixes + # Track if a requirements.txt file was found + if not requirements_txt_found and file_data.get("type") == "requirements.txt": + requirements_txt_found = True - for line in lines: - console.print(Padding(line, PADDING_VALUES), emoji=True) + # FileModel steps can be added here for the cve_details section support - # Provide a link for additional information - console.print( - Padding(MSG_LEARN_MORE.format(spec.remediation.more_info_url), PADDING_VALUES), emoji=True) - else: - # Handle files with no issues - console.print() - console.print(f"{ICON_CHECKMARK} [file_title]{path.relative_to(target)}: No issues found.[/file_title]", - emoji=True) + else: + # Handle the default case (GET implementation) + for path, analyzed_file in process_files(paths=file_paths, config=config, use_server_matching=False, obj=ctx.obj, target=target): + # Update counts and track vulnerabilities + count += len(analyzed_file.dependency_results.dependencies) + if exit_code == 0 and analyzed_file.dependency_results.failed: + exit_code = EXIT_CODE_VULNERABILITIES_FOUND + + affected_specifications = analyzed_file.dependency_results.get_affected_specifications() + affected_count += len(affected_specifications) + + # Sort vulnerabilities by severity + def sort_vulns_by_score(vuln: Vulnerability) -> int: + if vuln.severity and vuln.severity.cvssv3: + return vuln.severity.cvssv3.get("base_score", 0) + return 0 + + # Prepare to collect files needing fixes + to_fix_spec = [] + file_matched_for_fix = analyzed_file.file_type.value in fix_file_types + + # Handle files with affected specifications + if any(affected_specifications): + dependency_vuln_detected = detect_dependency_vulnerabilities(console, dependency_vuln_detected) + print_file_info(console, path, target) + + for spec in affected_specifications: + if file_matched_for_fix: + to_fix_spec.append(spec) + + # Print vulnerabilities for each specification + console.print() + vulns_to_report = sort_and_filter_vulnerabilities(spec.vulnerabilities, key_func=sort_vulns_by_score) + critical_vulns_count = count_critical_vulnerabilities(vulns_to_report) + vulns_found = len(vulns_to_report) + vuln_word = pluralize("vulnerability", vulns_found) + + msg = generate_vulnerability_message(spec.name, spec.raw, vulns_found, critical_vulns_count, vuln_word) + console.print(Padding(f"{msg}]", PADDING_VALUES), emoji=True, overflow="crop") + + # Display detailed vulnerability information if applicable + if detailed_output or vulns_found < MIN_DETAILED_OUTPUT_THRESHOLD: + render_vulnerabilities(vulns_to_report, console, detailed_output) + + # Generate remediation details and print them + lines, resolved_vulns, fixes = generate_remediation_details(spec, vuln_word, critical_vulns_count) + total_resolved_vulns += resolved_vulns + fixes_count += fixes + + for line in lines: + console.print(Padding(line, PADDING_VALUES), emoji=True) + + # Provide a link for additional information + console.print( + Padding(MSG_LEARN_MORE.format(spec.remediation.more_info_url), PADDING_VALUES), emoji=True) + else: + # Handle files with no issues + console.print() + console.print(f"{ICON_CHECKMARK} [file_title]{path.relative_to(target)}: No issues found.[/file_title]", + emoji=True) - # Track whether to suggest applying fixes - display_apply_fix_suggestion = should_display_fix_suggestion(ctx, analyzed_file, affected_specifications, apply_updates) + # Track whether to suggest applying fixes + display_apply_fix_suggestion = should_display_fix_suggestion(ctx, analyzed_file, affected_specifications, apply_updates) - # Track if a requirements.txt file was found - if not requirements_txt_found and analyzed_file.file_type is FileType.REQUIREMENTS_TXT: - requirements_txt_found = True + # Track if a requirements.txt file was found + if not requirements_txt_found and analyzed_file.file_type is FileType.REQUIREMENTS_TXT: + requirements_txt_found = True - # Save file data for further processing - file = FileModel(location=path, - file_type=analyzed_file.file_type, - results=analyzed_file.dependency_results) + # Save file data for further processing + file = FileModel(location=path, + file_type=analyzed_file.file_type, + results=analyzed_file.dependency_results) - if file_matched_for_fix: - to_fix_files.append((file, to_fix_spec)) + if file_matched_for_fix: + to_fix_files.append((file, to_fix_spec)) - files.append(file) + files.append(file) # Suggest fixes if applicable if display_apply_fix_suggestion: @@ -882,6 +1216,7 @@ def sort_vulns_by_score(vuln: Vulnerability) -> int: save_as=save_as if save_as and all(save_as) else None, detailed_output=detailed_output, filter_keys=filter_keys, + use_server_matching=use_server_matching **{k: v for k, v in ctx.params.items() if k not in {"detailed_output", "output", "save_as", "filter_keys"}} ) @@ -1202,7 +1537,7 @@ def system_scan(ctx: typer.Context, console.print(f"[bold]{prj}[/bold] at {data['path']}") for detail in [f"{prj} dashboard: {data['project_url']}"]: console.print(Padding(detail, (0, 0, 0, 1)), emoji=True, overflow="crop") - + process_report(ctx.obj, console, report, **{**ctx.params}) def get_vulnerability_summary(report: Dict[str, Any]) -> Tuple[int, int]: diff --git a/safety/scan/main.py b/safety/scan/main.py index 8df72f33..d50e3d1c 100644 --- a/safety/scan/main.py +++ b/safety/scan/main.py @@ -247,21 +247,61 @@ def build_meta(target: Path) -> Dict[str, Any]: "client": client_metadata, } -def process_files(paths: Dict[str, Set[Path]], config: Optional[ConfigModel] = None, use_server_matching: bool = False, obj=None, target=Path(".")) -> Generator[Tuple[Path, InspectableFile], None, None]: + +def get_report(paths: Dict[str, Set[Path]], config: Optional[ConfigModel] = None, use_server_matching: bool = False, obj=None, target=Path(".")): + files = [] + meta = build_meta(target) + meta["project_id"] = obj.project.id + for file_type_key, f_paths in paths.items(): + file_type = FileType(file_type_key) + if not file_type or not file_type.ecosystem: + continue + for f_path in f_paths: + relative_path = os.path.relpath(f_path, start=os.getcwd()) + try: + with open(f_path, "r") as file: + content = file.read() + except Exception as e: + LOG.error(f"Error reading file {f_path}: {e}") + continue + files.append({"name": relative_path, "content": content}) + + payload = {"meta": meta, "files": files} + response = obj.auth.client.upload_requirements(payload) + + if response.status_code == 200: + LOG.info("Scan Payload successfully sent to the API.") + return response.json() + + else: + LOG.error(f"Failed to send scan payload to the API. Status code: {response.status_code}") + LOG.error(f"Response: {response.text}") + raise SafetyError(f"Failed to send scan payload to the API. Status code: {response.status_code}, Response: {response.text}") + + + + +def process_files(paths: Dict[str, Set[Path]], config: Optional[ConfigModel] = None, use_server_matching: bool = False, obj=None, target=Path(".")): """ - Processes the files and yields each file path along with its inspectable file. + Processes the files and either yields each file path along with its inspectable file (GET implementation), + or sends all files in a single POST request (new POST implementation). Args: paths (Dict[str, Set[Path]]): A dictionary of file paths by file type. config (Optional[ConfigModel]): The configuration model (optional). + use_server_matching (bool): Whether to use server-side matching (POST implementation). + obj: Context object containing the authentication client. + target (Path): Target directory for metadata. - Yields: - Tuple[Path, InspectableFile]: A tuple of file path and inspectable file. + Returns: + Union[Generator[Tuple[Path, InspectableFile], None, None], Dict[str, Any]]: + - Generator of file path and inspectable file for the GET implementation. + - JSON response from the server for the POST implementation. """ + if not config: config = ConfigModel() - # old GET implementation if not use_server_matching: for file_type_key, f_paths in paths.items(): file_type = FileType(file_type_key) @@ -273,40 +313,3 @@ def process_files(paths: Dict[str, Set[Path]], config: Optional[ConfigModel] = N inspectable_file.inspect(config=config) inspectable_file.remediate() yield f_path, inspectable_file - - # new POST implementation - else: - files = [] - meta = build_meta(target) - for file_type_key, f_paths in paths.items(): - file_type = FileType(file_type_key) - if not file_type or not file_type.ecosystem: - continue - for f_path in f_paths: - relative_path = os.path.relpath(f_path, start=os.getcwd()) - # Read the file content - try: - with open(f_path, "r") as file: - content = file.read() - except Exception as e: - LOG.error(f"Error reading file {f_path}: {e}") - continue - # Append metadata to the payload - files.append({ - "name": relative_path, - "content": content, - }) - - # Prepare the payload with metadata at the top level - payload = { - "meta": meta, - "files": files, - } - - response = obj.auth.client.upload_requirements(payload) - - if response.status_code == 200: - LOG.info("Scan Payload successfully sent to the API.") - else: - LOG.error(f"Failed to send scan payload to the API. Status code: {response.status_code}") - LOG.error(f"Response: {response.text}") diff --git a/safety/scan/test.json b/safety/scan/test.json new file mode 100644 index 00000000..eaca4098 --- /dev/null +++ b/safety/scan/test.json @@ -0,0 +1,1076 @@ +{ + "meta": { + "stage": "development", + "scan_type": "scan", + "telemetry": { + "os_type": "Darwin", + "os_release": "24.0.0", + "safety_source": "cli", + "os_description": "macOS-15.0.1-arm64-arm-64bit-Mach-O", + "python_version": "3.13.1", + "safety_command": "scan", + "safety_options": {}, + "safety_version": "3.2.14" + }, + "timestamp": "2025-01-08T19:37:44.204202Z", + "authenticated": true, + "scan_locations": [ + "." + ], + "schema_version": "3.0", + "authentication_method": "token" + }, + "scan_results": { + "files": [], + "projects": [ + { + "id": "safety", + "git": { + "tag": "", + "dirty": true, + "branch": "test_repo", + "commit": "83165f661fd606d6791ca0f4d843bc9381e327d3", + "origin": "https://github.com/pyupio/safety.git" + }, + "files": [ + { + "type": "requirements.txt", + "location": "test_requirements.txt", + "categories": [ + "python" + ], + "results": { + "dependencies": [ + { + "name": "django", + "specifications": [ + { + "raw": "django==1.8.0", + "vulnerabilities": { + "remediation": { + "recommended": "4.2.17", + "other_recommended": [ + "5.1.4", + "5.0.10" + ], + "vulnerabilities_found": 58 + }, + "known_vulnerabilities": [ + { + "id": "51340", + "cve": { + "name": "CVE-2022-41323", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "In Django 3.2 before 3.2.16, 4.0 before 4.0.8, and 4.1 before 4.1.2, internationalized URLs were subject to a potential denial of service attack via the locale parameter, which is treated as a regular expression." + }, + "ignored": null, + "vulnerable_spec": "<3.2.16" + }, + { + "id": "38752", + "cve": { + "name": "CVE-2020-24584", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + }, + "advisory":"An issue was discovered in Django 2.2 before 2.2.16, 3.0 before 3.0.10, and 3.1 before 3.1.1 (when Python 3.7+ is used). The intermediate-level directories of the filesystem cache had the system's standard umask rather than 0o077." + }, + "ignored": null, + "vulnerable_spec": "<2.2.16" + }, + { + "id": "38749", + "cve": { + "name": "CVE-2020-24583", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + }, + "advisory": "Django 2.2.16, 3.0.10 and 3.1.1 include a fix for CVE-2020-24583: An issue was discovered in Django 2.2 before 2.2.16, 3.0 before 3.0.10, and 3.1 before 3.1.1 (when Python 3.7+ is used). FILE_UPLOAD_DIRECTORY_PERMISSIONS mode was not applied to intermediate-level directories created in the process of uploading files. It was also not applied to intermediate-level collected static directories when using the collectstatic management command.\\r\\n#NOTE: This vulnerability affects only users of Python versions above 3.7.\\r\\nhttps://www.djangoproject.com/weblog/2020/sep/01/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<2.2.16" + }, + { + "id": "25714", + "cve": { + "name": "CVE-2015-8213", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + }, + "cvssv3": null, + "advisory": "The get_format function in utils/formats.py in Django before 1.7.x before 1.7.11, 1.8.x before 1.8.7, and 1.9.x before 1.9rc2 might allow remote attackers to obtain sensitive application secrets via a settings key in place of a date/time format setting, as demonstrated by SECRET_KEY." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.7" + }, + { + "id": "60956", + "cve": { + "name": "CVE-2023-41164", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Affected versions of Django are vulnerable to potential Denial of Service via certain inputs with a very large number of Unicode characters in django.utils.encoding.uri_to_iri()." + }, + "ignored": null, + "vulnerable_spec": "<3.2.21" + }, + { + "id": "25725", + "cve": { + "name": "CVE-2015-5143", + "cvssv2": { + "base_score": 7.8, + "impact_score": 6.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:C" + }, + "cvssv3": null, + "advisory": "The session backends in Django before 1.4.21, 1.5.x through 1.6.x, 1.7.x before 1.7.9, and 1.8.x before 1.8.3 allows remote attackers to cause a denial of service (session store consumption) via multiple requests with unique session keys." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.3" + }, + { + "id": "25733", + "cve": { + "name": "CVE-2015-5145", + "cvssv2": { + "base_score": 7.8, + "impact_score": 6.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:C" + }, + "cvssv3": null, + "advisory": "validators.URLValidator in Django 1.8.x before 1.8.3 allows remote attackers to cause a denial of service (CPU consumption) via unspecified vectors." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.3" + }, + { + "id": "25726", + "cve": { + "name": "CVE-2015-5144", + "cvssv2": { + "base_score": 4.3, + "impact_score": 2.9, + "vector_string": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + "cvssv3": null, + "advisory": "Django before 1.4.21, 1.5.x through 1.6.x, 1.7.x before 1.7.9, and 1.8.x before 1.8.3 uses an incorrect regular expression, which allows remote attackers to inject arbitrary headers and conduct HTTP response splitting attacks via a newline character in an (1) email message to the EmailValidator, a (2) URL to the URLValidator, or unspecified vectors to the (3) validate_ipv4_address or (4) validate_slug validator." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.3" + }, + { + "id": "61586", + "cve": { + "name": "CVE-2023-43665", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Affected versions of Django are vulnerable to Denial-of-Service via django.utils.text.Truncator. The django.utils.text.Truncator chars() and words() methods (when used with html=True) are subject to a potential DoS (denial of service) attack via certain inputs with very long, potentially malformed HTML text. The chars() and words() methods are used to implement the truncatechars_html and truncatewords_html template filters, which are thus also vulnerable. NOTE: this issue exists because of an incomplete fix for CVE-2019-14232." + }, + "ignored": null, + "vulnerable_spec": "<3.2.22" + }, + { + "id": "72521", + "cve": { + "name": "CVE-2024-42005", + "cvssv2": null, + "cvssv3": { + "base_score": 7.3, + "impact_score": 3.4, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L" + }, + "advisory": "Affected versions of Django has a potential SQL injection vulnerability in the QuerySet.values() and QuerySet.values_list() methods. When used on models with a JSONField, these methods are susceptible to SQL injection through column aliases if a crafted JSON object key is passed as an argument." + }, + "ignored": null, + "vulnerable_spec": "<4.2.15" + }, + { + "id": "72515", + "cve": { + "name": "CVE-2024-41990", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Django addresses a memory exhaustion issue in django.utils.numberformat.floatformat(). When floatformat receives a string representation of a number in scientific notation with a large exponent, it could lead to excessive memory consumption. To prevent this, decimals with more than 200 digits are now returned as-is." + }, + "ignored": null, + "vulnerable_spec": "<4.2.15" + }, + { + "id": "72520", + "cve": { + "name": "CVE-2024-41991", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Django has a potential denial-of-service vulnerability in django.utils.html.urlize() and AdminURLFieldWidget. The urlize and urlizetrunc functions, along with AdminURLFieldWidget, are vulnerable to denial-of-service attacks when handling inputs with a very large number of Unicode characters." + }, + "ignored": null, + "vulnerable_spec": "<4.2.15" + }, + { + "id": "25721", + "cve": { + "name": "CVE-2016-6186", + "cvssv2": { + "base_score": 4.3, + "impact_score": 2.9, + "vector_string": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + "cvssv3": { + "base_score": 6.1, + "impact_score": 2.7, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "advisory": "Cross-site scripting (XSS) vulnerability in the dismissChangeRelatedObjectPopup function in contrib/admin/static/admin/js/admin/RelatedObjectLookups.js in Django before 1.8.14, 1.9.x before 1.9.8, and 1.10.x before 1.10rc1 allows remote attackers to inject arbitrary web script or HTML via vectors involving unsafe usage of Element.innerHTML." + }, + "ignored": null, + "vulnerable_spec": "<1.8.14" + }, + { + "id": "65771", + "cve": { + "name": "CVE-2024-27351", + "cvssv2": null, + "cvssv3": null, + "advisory": "Affected versions of Django are vulnerable to potential regular expression denial-of-service (REDoS). django.utils.text.Truncator.words() method (with html=True) and truncatewords_html template filter were subject to a potential regular expression denial-of-service attack using a suitably crafted string (follow up to CVE-2019-14232 and CVE-2023-43665)." + }, + "ignored": null, + "vulnerable_spec": "<3.2.25" + }, + { + "id": "39646", + "cve": { + "name": "CVE-2021-23336", + "cvssv2": { + "base_score": 4.0, + "impact_score": 4.9, + "vector_string": "AV:N/AC:H/Au:N/C:N/I:P/A:P" + }, + "cvssv3": { + "base_score": 5.9, + "impact_score": 4.2, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:L/A:H" + }, + "advisory":"Django versions 2.2.19, 3.0.13 and 3.1.7 include a fix for CVE-2021-23336: Web cache poisoning via 'django.utils.http.limited_parse_qsl()'. Django contains a copy of 'urllib.parse.parse_qsl' which was added to backport some security fixes. A further security fix has been issued recently such that 'parse_qsl(' no longer allows using ';' as a query parameter separator by default." + }, + "ignored": null, + "vulnerable_spec": "<2.2.19" + }, + { + "id": "33300", + "cve": { + "name": "CVE-2017-7233", + "cvssv2": { + "base_score": 5.8, + "impact_score": 4.9, + "vector_string": "AV:N/AC:M/Au:N/C:P/I:P/A:N" + }, + "cvssv3": { + "base_score": 6.1, + "impact_score": 2.7, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "advisory": "Django version 1.10.7, 1.9.13 and 1.8.18 include a fix for CVE-2017-7233: Django 1.10 before 1.10.7, 1.9 before 1.9.13, and 1.8 before 1.8.18 relies on user input in some cases to redirect the user to an " + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.18" + }, + { + "id": "39594", + "cve": { + "name": "CVE-2019-11358", + "cvssv2": { + "base_score": 4.3, + "impact_score": 2.9, + "vector_string": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + "cvssv3": { + "base_score": 6.1, + "impact_score": 2.7, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "advisory": "Django versions 2.1.9 and 2.2.2 include a patched bundled jQuery version to avoid a Prototype Pollution vulnerability." + }, + "ignored": null, + "vulnerable_spec": "<2.1.9" + }, + { + "id": "33075", + "cve": { + "name": "CVE-2016-9014", + "cvssv2": { + "base_score": 6.8, + "impact_score": 6.4, + "vector_string": "AV:N/AC:M/Au:N/C:P/I:P/A:P" + }, + "cvssv3": { + "base_score": 8.1, + "impact_score": 5.9, + "base_severity": "HIGH", + "vector_string": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "advisory": "Django before 1.8.x before 1.8.16, 1.9.x before 1.9.11, and 1.10.x before 1.10.3, when settings.DEBUG is True, allow remote attackers to conduct DNS rebinding attacks by leveraging failure to validate the HTTP Host header against settings.ALLOWED_HOSTS." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.16" + }, + { + "id": "33076", + "cve": { + "name": "CVE-2016-9013", + "cvssv2": { + "base_score": 7.5, + "impact_score": 6.4, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + "cvssv3": { + "base_score": 9.8, + "impact_score": 5.9, + "base_severity": "CRITICAL", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "advisory": "Django 1.8.x before 1.8.16, 1.9.x before 1.9.11, and 1.10.x before 1.10.3 use a hardcoded password for a temporary database user created when running tests with an Oracle database, which makes it easier for remote attackers to obtain access to the database server by leveraging failure to manually specify a password in the database settings TEST dictionary." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.16" + }, + { + "id": "40637", + "cve": { + "name": "CVE-2021-33203", + "cvssv2": { + "base_score": 4.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:S/C:P/I:N/A:N" + }, + "cvssv3": { + "base_score": 4.9, + "impact_score": 3.6, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N" + }, + "advisory": "Django before 2.2.24, 3.x before 3.1.12, and 3.2.x before 3.2.4 has a potential directory traversal via django.contrib.admindocs. Staff members could use the TemplateDetailView view to check the existence of arbitrary files. Additionally, if (and only if) the default admindocs templates have been customized by application developers to also show file contents, then not only the existence but also the file contents would have been exposed. In other words, there is directory traversal outside of the template root directories.\\r\\nhttps://www.djangoproject.com/weblog/2021/jun/02/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<2.2.24" + }, + { + "id": "40638", + "cve": { + "name": "CVE-2021-33571", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:P/A:N" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N" + }, + "advisory": "Django 2.2.24, 3.1.12, and 3.2.4 include a fix for CVE-2021-33571: In Django 2.2 before 2.2.24, 3.x before 3.1.12, and 3.2 before 3.2.4, URLValidator, validate_ipv4_address, and validate_ipv46_address do not prohibit leading zero characters in octal literals. This may allow a bypass of access control that is based on IP addresses. (validate_ipv4_address and validate_ipv46_address are unaffected with Python 3.9.5+).\\r\\nhttps://www.djangoproject.com/weblog/2021/jun/02/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<2.2.24" + }, + { + "id": "33074", + "cve": { + "name": "CVE-2016-2513", + "cvssv2": { + "base_score": 2.6, + "impact_score": 2.9, + "vector_string": "AV:N/AC:H/Au:N/C:P/I:N/A:N" + }, + "cvssv3": { + "base_score": 3.1, + "impact_score": 1.4, + "base_severity": "LOW", + "vector_string": "CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N" + }, + "advisory": "The password hasher in contrib/auth/hashers.py in Django before 1.8.10 and 1.9.x before 1.9.3 allows remote attackers to enumerate users via a timing attack involving login requests." + }, + "ignored": null, + "vulnerable_spec": "<1.8.10" + }, + { + "id": "33073", + "cve": { + "name": "CVE-2016-2512", + "cvssv2": { + "base_score": 4.3, + "impact_score": 2.9, + "vector_string": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + "cvssv3": { + "base_score": 7.4, + "impact_score": 4.0, + "base_severity": "HIGH", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:H/A:N" + }, + "advisory": "The utils.http.is_safe_url function in Django before 1.8.10 and 1.9.x before 1.9.3 allows remote attackers to redirect users to arbitrary web sites and conduct phishing attacks or possibly conduct cross-site scripting (XSS) attacks via a URL containing basic authentication, as demonstrated by http://mysite.example.com\\\\@attacker.com." + }, + "ignored": null, + "vulnerable_spec": "<1.8.10" + }, + { + "id": "55264", + "cve": { + "name": "CVE-2023-31047", + "cvssv2": null, + "cvssv3": { + "base_score": 9.8, + "impact_score": 5.9, + "base_severity": "CRITICAL", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "advisory":"Django 4.2.1, 4.1.9 and 3.2.19 include a fix for CVE-2023-31047: In Django 3.2 before 3.2.19, 4.x before 4.1.9, and 4.2 before 4.2.1, it was possible to bypass validation when using one form field to upload multiple files. This multiple upload has never been supported by forms.FileField or forms.ImageField (only the last uploaded file was validated). However, Django's documentation suggested otherwise.\\r\\nhttps://www.djangoproject.com/weblog/2023/may/03/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<3.2.19" + }, + { + "id": "37326", + "cve": { + "name": "CVE-2019-14232", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:P" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory":"Django 1.11.23, 2.1.11 and 2.2.4 include a fix for CVE-2019-14232: If django.utils.text.Truncator's chars() and words() methods were passed the html=True argument, they were extremely slow to evaluate certain inputs due to a catastrophic backtracking vulnerability in a regular expression. The chars() and words() methods are used to implement the truncatechars_html and truncatewords_html template filters, which were thus vulnerable." + }, + "ignored": null, + "vulnerable_spec": "<1.11.23" + }, + { + "id": "62126", + "cve": { + "name": "CVE-2023-46695", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Django 4.2.7, 4.1.13 and 3.2.23 include a fix for CVE-2023-46695: Potential denial of service vulnerability in UsernameField on Windows.\\r\\nhttps://www.djangoproject.com/weblog/2023/nov/01/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<3.2.23" + }, + { + "id": "36884", + "cve": { + "name": "CVE-2019-6975", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:P" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Django 1.11.19, 2.0.11 and 2.1.6 include a fix for CVE-2019-6975: Uncontrolled Memory Consumption via a malicious attacker-supplied value to the django.utils.numberformat.format() function." + }, + "ignored": null, + "vulnerable_spec": "<1.11.19" + }, + { + "id": "48040", + "cve": { + "name": "CVE-2022-28347", + "cvssv2": { + "base_score": 7.5, + "impact_score": 6.4, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + "cvssv3": { + "base_score": 9.8, + "impact_score": 5.9, + "base_severity": "CRITICAL", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "advisory": "Django 2.2.28, 3.2.13 and 4.0.4 include a fix for CVE-2022-28347: A SQL injection issue was discovered in QuerySet.explain() in Django 2.2 before 2.2.28, 3.2 before 3.2.13, and 4.0 before 4.0.4. This occurs by passing a crafted dictionary (with dictionary expansion) as the **options argument, and placing the injection payload in an option name.\\r\\nhttps://www.djangoproject.com/weblog/2022/apr/11/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<2.2.28" + }, + { + "id": "48041", + "cve": { + "name": "CVE-2022-28346", + "cvssv2": { + "base_score": 7.5, + "impact_score": 6.4, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + "cvssv3": { + "base_score": 9.8, + "impact_score": 5.9, + "base_severity": "CRITICAL", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "advisory": "Django 2.2.28, 3.2.13 and 4.0.4 include a fix for CVE-2022-28346: An issue was discovered in Django 2.2 before 2.2.28, 3.2 before 3.2.13, and 4.0 before 4.0.4. QuerySet.annotate(), aggregate(), and extra() methods are subject to SQL injection in column aliases via a crafted dictionary (with dictionary expansion) as the passed **kwargs.\\r\\nhttps://www.djangoproject.com/weblog/2022/apr/11/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<2.2.28" + }, + { + "id": "34918", + "cve": { + "name": "CVE-2017-12794", + "cvssv2": { + "base_score": 4.3, + "impact_score": 2.9, + "vector_string": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + "cvssv3": { + "base_score": 6.1, + "impact_score": 2.7, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "advisory":"Django 1.10.8 and 1.11.5 include a fix for CVE-2017-12794: In Django 1.10.x before 1.10.8 and 1.11.x before 1.11.5, HTML autoescaping was disabled in a portion of the template for the technical 500 debug page. Given the right circumstances, this allowed a cross-site scripting attack. This vulnerability shouldn't affect most production sites since you shouldn't run with \" (which makes this page accessible) in your production settings.\\r\\nhttps://www.djangoproject.com/weblog/2017/sep/05/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<1.10.8" + }, + { + "id": "35797", + "cve": { + "name": "CVE-2018-7536", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:P" + }, + "cvssv3": { + "base_score": 5.3, + "impact_score": 1.4, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" + }, + "advisory": "An issue was discovered in Django 2.0 before 2.0.3, 1.11 before 1.11.11, and 1.8 before 1.8.19. The django.utils.html.urlize() function was extremely slow to evaluate certain inputs due to catastrophic backtracking vulnerabilities in two regular expressions (only one regular expression for Django 1.8.x). The urlize() function is used to implement the urlize and urlizetrunc template filters, which were thus vulnerable. See: CVE-2018-7536." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.19" + }, + { + "id": "35796", + "cve": { + "name": "CVE-2018-7537", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:P" + }, + "cvssv3": { + "base_score": 5.3, + "impact_score": 1.4, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" + }, + "advisory":"Django 2.0.3, 1.8.19 and 1.11.11 include a fix for CVE-2018-7537: An issue was discovered in Django 2.0 before 2.0.3, 1.11 before 1.11.11, and 1.8 before 1.8.19. If django.utils.text.Truncator's chars() and words() methods were passed the html=True argument, they were extremely slow to evaluate certain inputs due to a catastrophic backtracking vulnerability in a regular expression. The chars() and words() methods are used to implement the truncatechars_html and truncatewords_html template filters, which were thus vulnerable." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.19" + }, + { + "id": "72109", + "cve": { + "name": "CVE-2024-39329", + "cvssv2": null, + "cvssv3": null, + "advisory": "Affected versions of Django are affected by a username enumeration vulnerability caused by timing differences in the django.contrib.auth.backends.ModelBackend.authenticate() method. This method allowed remote attackers to enumerate users through a timing attack involving login requests for users with unusable passwords. The timing difference in the authentication process exposed whether a username was valid or not, potentially aiding attackers in gaining unauthorized access." + }, + "ignored": null, + "vulnerable_spec": "<4.2.14" + }, + { + "id": "72110", + "cve": { + "name": "CVE-2024-39330", + "cvssv2": null, + "cvssv3": null, + "advisory": "Affected versions of Django are affected by a directory-traversal vulnerability in the Storage.save() method. Derived classes of the django.core.files.storage.Storage base class that overrides the generate_filename() method without replicating the file path validations existing in the parent class could allow for directory traversal via certain inputs when calling save(). This could enable an attacker to manipulate file paths and access unintended directories." + }, + "ignored": null, + "vulnerable_spec": "<4.2.14" + }, + { + "id": "72095", + "cve": { + "name": "CVE-2024-38875", + "cvssv2": null, + "cvssv3": null, + "advisory": "Affected versions of Django are affected by a potential denial-of-service vulnerability in the django.utils.html.urlize() function. The urlize and urlizetrunc template filters were susceptible to a denial-of-service attack via certain inputs containing many brackets. An attacker could exploit this vulnerability to cause significant delays or crashes in the affected application." + }, + "ignored": null, + "vulnerable_spec": "<4.2.14" + }, + { + "id": "72111", + "cve": { + "name": "CVE-2024-39614", + "cvssv2": null, + "cvssv3": null, + "advisory": "Affected versions of Django are potentially vulnerable to denial-of-service via the get_supported_language_variant() method. This method was susceptible to a denial-of-service attack when used with very long strings containing specific characters. Exploiting this vulnerability could cause significant delays or crashes in the affected application, potentially leading to service disruption." + }, + "ignored": null, + "vulnerable_spec": "<4.2.14" + }, + { + "id": "60132", + "cve": { + "name": "PVE-2023-60132", + "cvssv2": null, + "cvssv3": null, + "advisory": "Django 1.11.16, 2.0.9 and 2.1.1 include a fix for a Race Condition vulnerability that could lead to data loss.\\r\\nhttps://github.com/django/django/commit/221ef69a9b89262456bb7abe0e5a4b2fda4a0695" + }, + "ignored": null, + "vulnerable_spec": "<1.11.16" + }, + { + "id": "64976", + "cve": { + "name": "CVE-2024-24680", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Affected versions of Django are vulnerable to potential denial-of-service in intcomma template filter when used with very long strings." + }, + "ignored": null, + "vulnerable_spec": "<3.2.24" + }, + { + "id": "50454", + "cve": { + "name": "CVE-2022-36359", + "cvssv2": null, + "cvssv3": { + "base_score": 8.8, + "impact_score": 5.9, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H" + }, + "advisory": "Django 3.2.15 and 4.0.7 include a fix for CVE-2022-36359: An issue was discovered in the HTTP FileResponse class in Django 3.2 before 3.2.15 and 4.0 before 4.0.7. An application is vulnerable to a reflected file download (RFD) attack that sets the Content-Disposition header of a FileResponse when the filename is derived from user-supplied input.\\r\\nhttps://www.djangoproject.com/weblog/2022/aug/03/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<3.2.15" + }, + { + "id": "44742", + "cve": { + "name": "CVE-2022-22818", + "cvssv2": { + "base_score": 4.3, + "impact_score": 2.9, + "vector_string": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + "cvssv3": { + "base_score": 6.1, + "impact_score": 2.7, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "advisory": "The {% debug %} template tag in Django 2.2 before 2.2.27, 3.2 before 3.2.12, and 4.0 before 4.0.2 does not properly encode the current context. This may lead to XSS." + }, + "ignored": null, + "vulnerable_spec": "<2.2.27" + }, + { + "id": "44741", + "cve": { + "name": "CVE-2022-23833", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:P" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Django 2.2.27, 3.2.12 and 4.0.2 include a fix for CVE-2022-23833: Denial-of-service possibility in file uploads.\\r\\nhttps://www.djangoproject.com/weblog/2022/feb/01/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<2.2.27" + }, + { + "id": "52945", + "cve": { + "name": "CVE-2023-23969", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Django 3.2.17, 4.0.9 and 4.1.6 includes a fix for CVE-2023-23969: In Django 3.2 before 3.2.17, 4.0 before 4.0.9, and 4.1 before 4.1.6, the parsed values of Accept-Language headers are cached in order to avoid repetitive parsing. This leads to a potential denial-of-service vector via excessive memory usage if the raw value of Accept-Language headers is very large.\\r\\nhttps://www.djangoproject.com/weblog/2023/feb/01/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<3.2.17" + }, + { + "id": "53315", + "cve": { + "name": "CVE-2023-24580", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Django 4.1.7, 4.0.10 and 3.2.18 include a fix for CVE-2023-24580: Potential denial-of-service vulnerability in file uploads.\\r\\nhttps://www.djangoproject.com/weblog/2023/feb/14/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<3.2.18" + }, + { + "id": "25727", + "cve": { + "name": "CVE-2015-5963", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:P" + }, + "cvssv3": null, + "advisory": "contrib.sessions.middleware.SessionMiddleware in Django 1.8.x before 1.8.4, 1.7.x before 1.7.10, 1.4.x before 1.4.22, and possibly other versions allows remote attackers to cause a denial of service (session store consumption or session record removal) via a large number of requests to contrib.auth.views.logout, which triggers the creation of an empty session record." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.4" + }, + { + "id": "74396", + "cve": { + "name": "CVE-2024-53908", + "cvssv2": null, + "cvssv3": null, + "advisory": "Django affected versions are vulnerable to a potential SQL injection in the HasKey(lhs, rhs) lookup on Oracle databases. The vulnerability arises when untrusted data is directly used as the lhs value in the django.db.models.fields.json.HasKey lookup. However, applications using the jsonfield.has_key lookup with the __ syntax remain unaffected by this issue." + }, + "ignored": null, + "vulnerable_spec": "<4.2.17" + }, + { + "id": "74395", + "cve": { + "name": "CVE-2024-53907", + "cvssv2": null, + "cvssv3": null, + "advisory": "Affected versions of Django are vulnerable to a potential denial-of-service (DoS) attack in the `django.utils.html.strip_tags()` method. The vulnerability occurs when the `strip_tags()` method or the `striptags` template filter processes inputs containing large sequences of nested, incomplete HTML entities." + }, + "ignored": null, + "vulnerable_spec": "<4.2.17" + }, + { + "id": "73023", + "cve": { + "name": "CVE-2024-45230", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory":"A potential denial-of-service vulnerability has been identified in Django's urlize() and urlizetrunc() functions in django.utils.html. This vulnerability can be triggered by inputting huge strings containing a specific sequence of characters." + }, + "ignored": null, + "vulnerable_spec": "<4.2.16" + }, + { + "id": "73028", + "cve": { + "name": "CVE-2024-45231", + "cvssv2": null, + "cvssv3": { + "base_score": 5.3, + "impact_score": 1.4, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N" + }, + "advisory": "A security vulnerability has been discovered in certain versions of Django, affecting the password reset functionality. The PasswordResetForm class in django.contrib.auth.forms inadvertently allowed attackers to enumerate user email addresses by exploiting unhandled exceptions during the email sending process. This could be done by issuing password reset requests and observing the responses. Django has implemented a fix where these exceptions are now caught and logged using the django.contrib.auth logger, preventing potential information leakage through error responses." + }, + "ignored": null, + "vulnerable_spec": "<4.2.16" + }, + { + "id": "25718", + "cve": { + "name": "CVE-2016-7401", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:P/A:N" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N" + }, + "advisory": "The cookie parsing code in Django before 1.8.15 and 1.9.x before 1.9.10, when used on a site with Google Analytics, allows remote attackers to bypass an intended CSRF protection mechanism by setting arbitrary cookies." + }, + "ignored": null, + "vulnerable_spec": "<1.8.15" + }, + { + "id": "49733", + "cve": { + "name": "CVE-2022-34265", + "cvssv2": { + "base_score": 7.5, + "impact_score": 6.4, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + "cvssv3": { + "base_score": 9.8, + "impact_score": 5.9, + "base_severity": "CRITICAL", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "advisory": "Django 3.2.14 and 4.0.6 include a fix for CVE-2022-34265: Potential SQL injection via Trunc(kind) and Extract(lookup_name) arguments.\\r\\nhttps://www.djangoproject.com/weblog/2022/jul/04/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<3.2.14" + }, + { + "id": "43041", + "cve": { + "name": "CVE-2021-44420", + "cvssv2": { + "base_score": 7.5, + "impact_score": 6.4, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:P/A:P" + }, + "cvssv3": { + "base_score": 7.3, + "impact_score": 3.4, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L" + }, + "advisory": "Django versions 2.2.25, 3.1.14 and 3.2.10 include a fix for CVE-2021-44420: In Django 2.2 before 2.2.25, 3.1 before 3.1.14, and 3.2 before 3.2.10, HTTP requests for URLs with trailing newlines could bypass upstream access control based on URL paths.\\r\\nhttps://www.djangoproject.com/weblog/2021/dec/07/security-releases/" + }, + "ignored": null, + "vulnerable_spec": "<2.2.25" + }, + { + "id": "59293", + "cve": { + "name": "CVE-2023-36053", + "cvssv2": null, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Affected versions of Django are vulnerable to a potential ReDoS (regular expression denial of service) in EmailValidator and URLValidator via a very large number of domain name labels of emails and URLs." + }, + "ignored": null, + "vulnerable_spec": "<3.2.20" + }, + { + "id": "40404", + "cve": { + "name": "CVE-2021-31542", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + }, + "advisory": "Django 2.2.21, 3.1.9 and 3.2.1 include a fix for CVE-2021-31542: MultiPartParser, UploadedFile, and FieldFile allowed directory traversal via uploaded files with suitably crafted file names.\\r\\nhttps://www.djangoproject.com/weblog/2021/may/04/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<2.2.21" + }, + { + "id": "25732", + "cve": { + "name": "CVE-2015-3982", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:P/A:N" + }, + "cvssv3": null, + "advisory": "The session.flush function in the cached_db backend in Django 1.8.x before 1.8.2 does not properly flush the session, which allows remote attackers to hijack user sessions via an empty string in the session key." + }, + "ignored": null, + "vulnerable_spec": ">=1.8a1,<1.8.2" + }, + { + "id": "35740", + "cve": { + "name": "CVE-2017-7234", + "cvssv2": { + "base_score": 5.8, + "impact_score": 4.9, + "vector_string": "AV:N/AC:M/Au:N/C:P/I:P/A:N" + }, + "cvssv3": { + "base_score": 6.1, + "impact_score": 2.7, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" + }, + "advisory":"Django versions 1.10.7, 1.9.13 and 1.8.18 include a fix for CVE-2017-7234: A maliciously crafted URL to a Django (1.10 before 1.10.7, 1.9 before 1.9.13, and 1.8 before 1.8.18) site using the 'django.views.static.serve()' view could redirect to any other domain, aka an open redirect vulnerability.\\r\\nhttps://www.djangoproject.com/weblog/2017/apr/04/security-releases/\\r\\nhttp://www.debian.org/security/2017/dsa-3835\\r\\nhttp://www.securityfocus.com/bid/97401\\r\\nhttp://www.securitytracker.com/id/1038177" + }, + "ignored": null, + "vulnerable_spec": ">=1.8.0a1,<1.8.18" + }, + { + "id": "44427", + "cve": { + "name": "CVE-2021-45116", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + }, + "advisory":"Django 2.2.26, 3.2.11 and 4.0.1 include a fix for CVE-2021-45116: An issue was discovered in Django 2.2 before 2.2.26, 3.2 before 3.2.11, and 4.0 before 4.0.1. Due to leveraging the Django Template Language's variable resolution logic, the dictsort template filter was potentially vulnerable to information disclosure, or an unintended method call, if passed a suitably crafted key.\\r\\nhttps://www.djangoproject.com/weblog/2022/jan/04/security-releases" + }, + "ignored": null, + "vulnerable_spec": "<2.2.26" + }, + { + "id": "44423", + "cve": { + "name": "CVE-2021-45115", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:N/I:N/A:P" + }, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + "advisory": "Django 2.2.26, 3.2.11 and 4.0.1 include a fix for CVE-2021-45115: UserAttributeSimilarityValidator incurred significant overhead in evaluating a submitted password that was artificially large in relation to the comparison values. In a situation where access to user registration was unrestricted, this provided a potential vector for a denial-of-service attack.\\r\\nhttps://www.djangoproject.com/weblog/2022/jan/04/security-releases/" + }, + "ignored": null, + "vulnerable_spec": "<2.2.26" + }, + { + "id": "44426", + "cve": { + "name": "CVE-2021-45452", + "cvssv2": { + "base_score": 5.0, + "impact_score": 2.9, + "vector_string": "AV:N/AC:L/Au:N/C:P/I:N/A:N" + }, + "cvssv3": { + "base_score": 5.3, + "impact_score": 1.4, + "base_severity": "MEDIUM", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N" + }, + "advisory": "Django 2.2.26, 3.2.11 and 4.0.1 include a fix for CVE-2021-45452: Storage.save in Django 2.2 before 2.2.26, 3.2 before 3.2.11, and 4.0 before 4.0.1 allows directory traversal if crafted filenames are directly passed to it.\\r\\nhttps://www.djangoproject.com/weblog/2022/jan/04/security-releases/" + }, + "ignored": null, + "vulnerable_spec": "<2.2.26" + } + ] + } + } + ] + } + ] + } + } + ], + "policy": { + "id": "", + "path": null, + "source": "cloud" + }, + "upload_request_id": null, + "location": "" + } + ] + } +} diff --git a/safety/scan/test_json.py b/safety/scan/test_json.py new file mode 100644 index 00000000..3a23089a --- /dev/null +++ b/safety/scan/test_json.py @@ -0,0 +1,84 @@ +test_json = { + "meta": { + "stage": "cicd", + "scan_type": "scan", + "telemetry": { + "os_type": "Darwin", + "os_release": "24.0.0", + "safety_source": "cli", + "os_description": "macOS-15.0.1-arm64-arm-64bit-Mach-O", + "python_version": "3.13.1", + "safety_command": "scan", + "safety_options": {}, + "safety_version": "3.2.14" + }, + "timestamp": "2025-01-06T16:48:13.830470Z", + "authenticated": True, + "scan_locations": ["."], + "schema_version": "3.0", + "authentication_method": "token" + }, + "results": {}, + "scan_results": { + "files": [], + "projects": [ + { + "id": "safety", + "git": { + "tag": "", + "dirty": True, + "branch": "test_repo", + "commit": "9f9babca82839121ff7d2543af90181113c4c467", + "origin": "https://github.com/pyupio/safety.git" + }, + "files": [{ + "type": "requirements.txt", + "location": "test_requirements.txt", + "categories": ["python"], + "results": { + "dependencies": [ + { + "name": "django", + "specifications": [ + { + "raw": "django==1.8.0", + "vulnerabilities": { + "remediation": { + "recommended": "4.2.17", + "other_recommended": ["5.1.4", "5.0.10"], + "vulnerabilities_found": 12 + }, + "known_vulnerabilities": [ + { + "id": "59293", + "advisory": "Affected versions of Django are vulnerable to a potential ReDoS (regular expression denial of service) in EmailValidator and URLValidator via a very large number of domain name labels of emails and URLs.", + "cve": { + "name": "CVE-2023-36053", + "cvssv2": None, + "cvssv3": { + "base_score": 7.5, + "impact_score": 3.6, + "base_severity": "HIGH", + "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + } + }, + "ignored": { + "code": None, + "expires": None, + "reason": None + }, + "vulnerable_spec": ">=4.0a1,<4.1.10" + }, + ] + } + } + ] + }, + ] + } + } + ] + } + ] + } +}