diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4f89320b05..d936d2c4fc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: hooks: - id: pyupgrade exclude: ^fuzz/generated/ - args: ["--py38-plus"] + args: ["--py39-plus"] - repo: https://github.com/pycqa/flake8 rev: 7.3.0 diff --git a/cve_bin_tool/checkers/__init__.py b/cve_bin_tool/checkers/__init__.py index bab000ccb3..591b29d589 100644 --- a/cve_bin_tool/checkers/__init__.py +++ b/cve_bin_tool/checkers/__init__.py @@ -15,10 +15,7 @@ from importlib import metadata as importlib_metadata else: import importlib_metadata -if sys.version_info >= (3, 9): - import importlib.resources as resources -else: - import importlib_resources as resources +import importlib.resources as resources __all__ = [ "Checker", diff --git a/cve_bin_tool/cli.py b/cve_bin_tool/cli.py index 451783ec01..0f3cc72fd5 100644 --- a/cve_bin_tool/cli.py +++ b/cve_bin_tool/cli.py @@ -94,12 +94,18 @@ def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) -def main(argv=None): - """Scan a binary file for certain open source libraries that may have CVEs""" - if sys.version_info < (3, 8): +def check_python_version() -> None: + """Check if the current Python version is supported. Raise OSError if not.""" + if sys.version_info < (3, 9): raise OSError( - "Python no longer provides security updates for version 3.7 as of June 2023. Please upgrade to python 3.8+ to use CVE Binary Tool." + "Python no longer provides security updates for version 3.8 as of October 2024. " + "Please upgrade to Python 3.9+ to use CVE Binary Tool." ) + + +def main(argv=None): + """Scan a binary file for certain open source libraries that may have CVEs""" + check_python_version() argv = argv or sys.argv # Reset logger level to info diff --git a/cve_bin_tool/config.py b/cve_bin_tool/config.py index e9f6e647a3..d8d3304efc 100644 --- a/cve_bin_tool/config.py +++ b/cve_bin_tool/config.py @@ -5,9 +5,9 @@ import sys from collections import ChainMap +from collections.abc import Mapping from logging import Logger from pathlib import Path -from typing import Mapping if sys.version_info >= (3, 11): import tomllib as toml diff --git a/cve_bin_tool/csv2cve.py b/cve_bin_tool/csv2cve.py index dee8ef1a90..30b92ac5e8 100644 --- a/cve_bin_tool/csv2cve.py +++ b/cve_bin_tool/csv2cve.py @@ -16,10 +16,7 @@ def main(argv: list[str] | None = None): """Used to scan a .csv file that lists the dependencies.""" - if sys.version_info < (3, 8): - raise OSError( - "Python no longer provides security updates for version 3.7 as of June 2023. Please upgrade to python 3.8+ to use CVE Binary Tool." - ) + cli.check_python_version() logger: logging.Logger = LOGGER.getChild("CSV2CVE") argv = argv or sys.argv if len(argv) < 2: diff --git a/cve_bin_tool/cve_scanner.py b/cve_bin_tool/cve_scanner.py index f353dc2b09..65c13a99a7 100644 --- a/cve_bin_tool/cve_scanner.py +++ b/cve_bin_tool/cve_scanner.py @@ -7,7 +7,7 @@ from logging import Logger from pathlib import Path from string import ascii_lowercase -from typing import DefaultDict, Dict, List +from typing import DefaultDict from rich.console import Console @@ -28,12 +28,12 @@ class CVEScanner: products_with_cve: int products_without_cve: int all_cve_data: DefaultDict[ProductInfo, CVEData] - all_cve_version_info: Dict[str, VersionInfo] + all_cve_version_info: dict[str, VersionInfo] RANGE_UNSET: str = "" dbname: str = str(Path(DISK_LOCATION_DEFAULT) / DBNAME) CONSOLE: Console = Console(file=sys.stderr, theme=cve_theme) - ALPHA_TO_NUM: Dict[str, int] = dict(zip(ascii_lowercase, range(26))) + ALPHA_TO_NUM: dict[str, int] = dict(zip(ascii_lowercase, range(26))) def __init__( self, @@ -44,8 +44,8 @@ def __init__( logger: Logger = None, error_mode: ErrorMode = ErrorMode.TruncTrace, check_exploits: bool = False, - exploits_list: List[str] = [], - disabled_sources: List[str] = [], + exploits_list: list[str] = [], + disabled_sources: list[str] = [], no_scan: bool = False, ): self.score = score @@ -229,10 +229,10 @@ def get_cves(self, product_info: ProductInfo, triage_data: TriageData): ) product_info_data: CVEData | None = self.all_cve_data.get(product_info) - prev_cves: List[CVE] = ( + prev_cves: list[CVE] = ( product_info_data.get("cves", []) if product_info_data is not None else [] # type: ignore ) - cves: List[CVE] = [] + cves: list[CVE] = [] # Go through and get all the severities if cve_list: @@ -403,7 +403,7 @@ def filter_triage_data(self): Filter out triage data that is not relevant to the CVEs found, specifically those marked as NotAffected or FalsePositives. """ - to_delete: List[ProductInfo] = [] + to_delete: list[ProductInfo] = [] for product_info, cve_data in self.all_cve_data.items(): original_cves = cve_data["cves"] diff --git a/cve_bin_tool/data_sources/__init__.py b/cve_bin_tool/data_sources/__init__.py index bb2b186c30..87d046f967 100644 --- a/cve_bin_tool/data_sources/__init__.py +++ b/cve_bin_tool/data_sources/__init__.py @@ -3,14 +3,9 @@ from __future__ import annotations -import sys +import importlib.resources as resources from abc import ABC, abstractmethod -if sys.version_info >= (3, 9): - import importlib.resources as resources -else: - import importlib_resources as resources - class Data_Source(ABC): @abstractmethod diff --git a/cve_bin_tool/helper_script.py b/cve_bin_tool/helper_script.py index c3f51f454a..c0a9182be8 100644 --- a/cve_bin_tool/helper_script.py +++ b/cve_bin_tool/helper_script.py @@ -8,9 +8,9 @@ import sys import textwrap from collections import ChainMap +from collections.abc import MutableMapping from logging import Logger from pathlib import Path -from typing import MutableMapping from rich import print as rprint from rich.console import Console diff --git a/cve_bin_tool/input_engine.py b/cve_bin_tool/input_engine.py index 3a90f959b4..3a944dc017 100644 --- a/cve_bin_tool/input_engine.py +++ b/cve_bin_tool/input_engine.py @@ -11,9 +11,10 @@ import csv import json from collections import defaultdict +from collections.abc import Iterable from logging import Logger from pathlib import Path -from typing import Any, DefaultDict, Dict, Iterable, Set, Union +from typing import Any, DefaultDict, Union from cve_bin_tool.cvedb import CVEDB from cve_bin_tool.error_handler import ( @@ -27,7 +28,7 @@ from cve_bin_tool.util import ProductInfo, Remarks # TriageData is dictionary of cve_number mapped to dictionary of remarks, comments and custom severity -TriageData = Dict[str, Union[Dict[str, Any], Set[str]]] +TriageData = dict[str, Union[dict[str, Any], set[str]]] class InputEngine: @@ -135,7 +136,7 @@ def input_json(self) -> None: self.parse_data(set(json_data[0].keys()), json_data) - def parse_data(self, fields: Set[str], data: Iterable) -> None: + def parse_data(self, fields: set[str], data: Iterable) -> None: """ Parses common data structure for CSV and JSON input formats. diff --git a/cve_bin_tool/package_list_parser.py b/cve_bin_tool/package_list_parser.py index d2f7b206bd..d63f5e34f1 100644 --- a/cve_bin_tool/package_list_parser.py +++ b/cve_bin_tool/package_list_parser.py @@ -7,7 +7,7 @@ from logging import Logger from pathlib import Path from subprocess import PIPE, run -from typing import Any, Dict, List +from typing import Any import distro @@ -69,10 +69,10 @@ def __init__( self.logger = logger self.error_mode = error_mode - self.parsed_data_without_vendor: Dict[Any, Any] = defaultdict(dict) - self.parsed_data_with_vendor: Dict[Any, Any] = defaultdict(dict) - self.package_names_with_vendor: List[Any] = [] - self.package_names_without_vendor: List[Any] = [] + self.parsed_data_without_vendor: dict[Any, Any] = defaultdict(dict) + self.parsed_data_with_vendor: dict[Any, Any] = defaultdict(dict) + self.package_names_with_vendor: list[Any] = [] + self.package_names_without_vendor: list[Any] = [] def parse_list(self): """ diff --git a/cve_bin_tool/parsers/parse.py b/cve_bin_tool/parsers/parse.py index 343d30cfc4..e679da7f28 100644 --- a/cve_bin_tool/parsers/parse.py +++ b/cve_bin_tool/parsers/parse.py @@ -10,10 +10,7 @@ from importlib import metadata as importlib_metadata else: import importlib_metadata -if sys.version_info >= (3, 9): - import importlib.resources as resources -else: - import importlib_resources as resources +import importlib.resources as resources from cve_bin_tool.parsers import Parser diff --git a/cve_bin_tool/strings.py b/cve_bin_tool/strings.py index 7ae84b8d36..61e0987f3d 100644 --- a/cve_bin_tool/strings.py +++ b/cve_bin_tool/strings.py @@ -8,7 +8,7 @@ """ import subprocess -from typing import ClassVar, List, Set +from typing import ClassVar from cve_bin_tool.async_utils import FileIO, run_coroutine from cve_bin_tool.util import inpath @@ -18,7 +18,7 @@ class Strings: """Utility class for parsing files and extracting printable characters.""" # printable characters - PRINTABLE: ClassVar[Set[int]] = set(range(32, 128)) + PRINTABLE: ClassVar[set[int]] = set(range(32, 128)) # add tab to the printable character PRINTABLE.add(9) @@ -35,7 +35,7 @@ async def aio_parse(self) -> str: str: The acuumulated printable characters from the file. """ async with FileIO(self.filename, "rb") as f: - tmp: List[str] = [] + tmp: list[str] = [] async for line in f: for char in line: # remove all unprintable characters diff --git a/cve_bin_tool/util.py b/cve_bin_tool/util.py index 11ee0533f4..3a106508a8 100644 --- a/cve_bin_tool/util.py +++ b/cve_bin_tool/util.py @@ -9,9 +9,11 @@ import os import re import sys +from collections.abc import Iterator from enum import Enum from pathlib import Path -from typing import DefaultDict, Iterator, List, NamedTuple, Pattern, Set, Union +from re import Pattern +from typing import DefaultDict, NamedTuple, Union import requests from packageurl import PackageURL @@ -248,7 +250,7 @@ class VersionInfo(NamedTuple): end_excluding: str -class CVEData(DefaultDict[str, Union[List[CVE], Set[str]]]): +class CVEData(DefaultDict[str, Union[list[CVE], set[str]]]): """ A Class representing a dictionary of CVEs and paths """ diff --git a/cve_bin_tool/version_scanner.py b/cve_bin_tool/version_scanner.py index 4d7ba2489d..3ddc46e4ec 100644 --- a/cve_bin_tool/version_scanner.py +++ b/cve_bin_tool/version_scanner.py @@ -4,9 +4,9 @@ import subprocess import sys +from collections.abc import Iterator from logging import Logger from pathlib import Path -from typing import Iterator from cve_bin_tool.checkers import BUILTIN_CHECKERS, Checker from cve_bin_tool.cvedb import CVEDB diff --git a/cve_bin_tool/vex_manager/generate.py b/cve_bin_tool/vex_manager/generate.py index 543faa7d56..2dc3611116 100644 --- a/cve_bin_tool/vex_manager/generate.py +++ b/cve_bin_tool/vex_manager/generate.py @@ -3,7 +3,7 @@ from logging import Logger from pathlib import Path -from typing import Dict, List, Optional +from typing import Optional from lib4sbom.data.vulnerability import Vulnerability from lib4vex.generator import VEXGenerator @@ -67,7 +67,7 @@ def __init__( vendor: str, filename: str, vextype: str, - all_cve_data: Dict[ProductInfo, CVEData], + all_cve_data: dict[ProductInfo, CVEData], revision_reason: str = "", sbom_serial_number: str = "", sbom: Optional[str] = None, @@ -154,7 +154,7 @@ def __generate_vex_filename(self) -> str: ) return str(filename) - def __get_metadata(self) -> Dict: + def __get_metadata(self) -> dict: """ Generates metadata for the VEX document based on the specified VEX type, product, release, and vendor information. @@ -183,7 +183,7 @@ def __get_metadata(self) -> Dict: return metadata - def __get_vulnerabilities(self) -> List[Vulnerability]: + def __get_vulnerabilities(self) -> list[Vulnerability]: """ Retrieves and constructs a list of vulnerability objects based on the current CVE data. diff --git a/cve_bin_tool/vex_manager/parse.py b/cve_bin_tool/vex_manager/parse.py index b58d1fe1e0..150eb9174b 100644 --- a/cve_bin_tool/vex_manager/parse.py +++ b/cve_bin_tool/vex_manager/parse.py @@ -1,14 +1,14 @@ # Copyright (C) 2024 Intel Corporation # SPDX-License-Identifier: GPL-3.0-or-later -from typing import Any, DefaultDict, Dict, Set, Union +from typing import Any, DefaultDict, Union from lib4vex.parser import VEXParser from cve_bin_tool.log import LOGGER from cve_bin_tool.util import ProductInfo, Remarks, decode_bom_ref, decode_purl -TriageData = Dict[str, Union[Dict[str, Any], Set[str]]] +TriageData = dict[str, Union[dict[str, Any], set[str]]] class VEXParse: diff --git a/dev-requirements.txt b/dev-requirements.txt index 64de1a9dcb..ffba12c8ab 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,15 +1,10 @@ -bandit; python_version <= "3.8" -bandit==1.8.6; python_version > "3.8" -black==25.1.0; python_version > "3.8" -black; python_version <= "3.8" +bandit==1.8.6 +black==25.1.0 build -isort; python_version < "3.8" -isort==6.0.1; python_version >= "3.8" -pre-commit; python_version <= "3.8" -pre-commit==4.3.0; python_version > "3.8" +isort==6.0.1 +pre-commit==4.3.0 codespell==v2.4.1 -flake8; python_version < "3.8" -flake8==7.3.0; python_version >= "3.8" +flake8==7.3.0 gitlint==v0.19.1 interrogate jsonschema diff --git a/doc/MANUAL.md b/doc/MANUAL.md index 77d689231a..38d599f4ed 100644 --- a/doc/MANUAL.md +++ b/doc/MANUAL.md @@ -550,7 +550,7 @@ This data source provides the CVEs for the CURL product. ## Limitations The last release of this tool to support python 2.7 is 0.3.1. Please use -python 3.8+ for development and future versions. Linux and Windows are +currently supported Python for development and future versions. Linux and Windows are supported, as is usage within cygwin on windows. This tool does not scan for all possible known public vulnerabilities, it only diff --git a/requirements.txt b/requirements.txt index e411ca20d2..932444f8f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,6 @@ distro filetype>=1.2.0 gsutil importlib_metadata>=3.6; python_version < "3.10" -importlib_resources; python_version < "3.9" jinja2>=2.11.3 jsonschema>=3.0.2 lib4sbom>=0.8.7 diff --git a/test/README.md b/test/README.md index d04a607a4a..c1bafca412 100644 --- a/test/README.md +++ b/test/README.md @@ -74,14 +74,14 @@ The recommended way to do this yourself is to use python's `virtualenv` You can set up virtualenv for all these environments: ```console -virtualenv -p python3.8 venv3.8 -virtualenv -p python3.9 venv3.9 +virtualenv -p python3.11 venv3.11 +virtualenv -p python3.12 venv3.12 ``` -To activate one of these (the example uses 3.8), run the tests, and deactivate: +To activate one of these (the example uses 3.12), run the tests, and deactivate: ```console -source venv3.8/bin/activate +source venv3.12/bin/activate pytest deactivate diff --git a/test/language_data/requirements.txt b/test/language_data/requirements.txt index 1d4aa9a090..27a8edff20 100644 --- a/test/language_data/requirements.txt +++ b/test/language_data/requirements.txt @@ -11,9 +11,8 @@ zstandard; python_version >= "3.4" distro defusedxml xmlschema -importlib_metadata; python_version < "3.8" requests -urllib3>=1.26.5 # dependency of requests added explictly to avoid CVEs +urllib3>=1.26.5 # dependency of requests added explicitly to avoid CVEs gsutil cvss packaging diff --git a/test/test_extractor.py b/test/test_extractor.py index 8b72cf791f..968555dc2f 100644 --- a/test/test_extractor.py +++ b/test/test_extractor.py @@ -322,8 +322,7 @@ def extension_list(self) -> list[str]: @pytest.mark.asyncio @pytest.mark.skipif( - sys.version_info.major == 3 and (sys.version_info.minor in (7, 11)), - reason="py3.7 and py3.11 fail sometimes", + sys.version_info[:2] == (3, 11), reason="py3.11 fails sometimes" ) @pytest.mark.skipif( sys.platform == "win32", reason="windows zst support incomplete"