Skip to content

refactor: Fixes #4578 #4938

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.0
Expand Down
2 changes: 1 addition & 1 deletion cve_bin_tool/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def main(argv=None):
"""Scan a binary file for certain open source libraries that may have CVEs"""
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."
"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."
)
argv = argv or sys.argv

Expand Down
2 changes: 1 addition & 1 deletion cve_bin_tool/csv2cve.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,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."
"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."
)
logger: logging.Logger = LOGGER.getChild("CSV2CVE")
argv = argv or sys.argv
Expand Down
19 changes: 10 additions & 9 deletions cve_bin_tool/cve_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from logging import Logger
from pathlib import Path
from string import ascii_lowercase
from typing import DefaultDict, Dict, List

from rich.console import Console

Expand All @@ -19,6 +18,8 @@
from cve_bin_tool.util import CVE, CVEData, ProductInfo, Remarks, VersionInfo
from cve_bin_tool.version_compare import Version

# from typing import Dict, List


class CVEScanner:
"""
Expand All @@ -27,13 +28,13 @@ 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_data: defaultdict[ProductInfo, CVEData]
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,
Expand All @@ -44,8 +45,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] = [],
):
self.logger = logger or LOGGER.getChild(self.__class__.__name__)
self.error_mode = error_mode
Expand Down Expand Up @@ -211,10 +212,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:
Expand Down Expand Up @@ -385,7 +386,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"]
Expand Down
17 changes: 9 additions & 8 deletions cve_bin_tool/input_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, Union

from cve_bin_tool.cvedb import CVEDB
from cve_bin_tool.error_handler import (
Expand All @@ -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:
Expand All @@ -37,13 +38,13 @@ class InputEngine:
This class is responsible for parsing various input file formats (CSV, JSON) in the CVE Bin Tool.

Attributes:
- parsed_data (DefaultDict[ProductInfo, TriageData]): Dictionary containing parsed input data.
- parsed_data (defaultdict[ProductInfo, TriageData]): Dictionary containing parsed input data.

Methods:
- __init__(self, filename: str, logger: Logger = None, error_mode=ErrorMode.TruncTrace, filetype="autodetect"):
Initializes the InputEngine with the specified filename, logger, error mode, and filetype.

- parse_input(self) -> DefaultDict[ProductInfo, TriageData]:
- parse_input(self) -> defaultdict[ProductInfo, TriageData]:
Parses the input file based on its type (CSV, JSON) and returns the parsed data.

- input_csv(self) -> None:
Expand All @@ -58,7 +59,7 @@ class InputEngine:
"""

# parsed_data is a dictionary of vendor, product, version mapped to TriageData
parsed_data: DefaultDict[ProductInfo, TriageData]
parsed_data: defaultdict[ProductInfo, TriageData]

def __init__(
self,
Expand All @@ -85,12 +86,12 @@ def __init__(
# Connect to the database
self.cvedb = CVEDB(version_check=False)

def parse_input(self) -> DefaultDict[ProductInfo, TriageData]:
def parse_input(self) -> defaultdict[ProductInfo, TriageData]:
"""
Parses the input file and returns the parsed data.

Returns:
- DefaultDict[ProductInfo, TriageData]: Parsed input data.
- defaultdict[ProductInfo, TriageData]: Parsed input data.

"""

Expand Down Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions cve_bin_tool/output_engine/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def group_cve_by_remark(
{
"cve_number": "CVE-XXX-XXX",
"severity": "High",
"decription: "Lorem Ipsm",
"description: "Lorem Ipsm",
},
{...}
],
Expand All @@ -329,7 +329,7 @@ def group_cve_by_remark(
cve_by_product (List[CVE]): List of CVE(s) that needs to be grouped

Returns:
DefaultDict[Remarks, List[Dict[str, str]]]: CVEs grouped by remark stored in default dict
defaultdict[Remarks, list[dict[str, str]]]: CVEs grouped by remark stored in default dict
"""
cve_by_remarks: defaultdict[Remarks, list[dict[str, str]]] = defaultdict(list)
for cve in cve_by_product:
Expand Down
4 changes: 2 additions & 2 deletions cve_bin_tool/sbom_manager/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class SBOMParse:
It provides methods for scanning SBOM files, parsing them, and retrieving vendor information.

Attributes:
- sbom_data (DefaultDict[ProductInfo, TriageData]): Dictionary containing parsed SBOM data.
- sbom_data (defaultdict[ProductInfo, TriageData]): Dictionary containing parsed SBOM data.

"""

Expand Down Expand Up @@ -147,7 +147,7 @@ def common_prefix_split(self, product, version) -> list[ProductInfo]:
if not found_common_prefix:
# if vendor not found after removing common prefix try splitting it
LOGGER.debug(
f"No Vendor found for {product}, trying splitted product. "
f"No Vendor found for {product}, trying split product. "
"Some results may be inaccurate due to vendor identification limitations."
)
splitted_product = product.split("-")
Expand Down
6 changes: 3 additions & 3 deletions cve_bin_tool/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions cve_bin_tool/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
import os
import re
import sys
from collections import defaultdict
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 NamedTuple, Union

import requests
from packageurl import PackageURL
Expand Down Expand Up @@ -248,7 +251,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
"""
Expand Down
9 changes: 5 additions & 4 deletions cve_bin_tool/vex_manager/parse.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: GPL-3.0-or-later

from typing import Any, DefaultDict, Dict, Set, Union
from collections import defaultdict
from typing import Any, 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:
Expand All @@ -24,7 +25,7 @@ class VEXParse:

Methods:
- __init__(self, filename: str, vextype: str, logger=None): Initializes the VEXParse object.
- parse_vex(self) -> DefaultDict[ProductInfo, TriageData]: Parses the VEX file and extracts the necessary fields from the vulnerabilities.
- parse_vex(self) -> defaultdict[ProductInfo, TriageData]: Parses the VEX file and extracts the necessary fields from the vulnerabilities.
- process_metadata(self) -> None: Processes the metadata.
- process_product(self) -> None: Processes the product information.
- process_vulnerabilities(self, vulnerabilities) -> None: Processes the vulnerabilities and extracts the necessary fields.
Expand Down Expand Up @@ -63,7 +64,7 @@ def __init__(self, filename: str, vextype: str, logger=None):
self.parsed_data = {}
self.serialNumbers = set()

def parse_vex(self) -> DefaultDict[ProductInfo, TriageData]:
def parse_vex(self) -> defaultdict[ProductInfo, TriageData]:
"""Parses the VEX file and extracts the necessary fields from the vulnerabilities."""
vexparse = VEXParser(vex_type=self.vextype)
vexparse.parse(self.filename)
Expand Down
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ types-jsonschema
types-PyYAML
types-requests
types-setuptools
types-toml
types-toml
12 changes: 6 additions & 6 deletions doc/MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ On Windows, it requires
- `Expand`

Windows has `ar` and `Expand` installed in default, but `7z` in particular might need to be installed.
If you wan to run our test-suite or scan a zstd compressed file, We recommend installing this [7-zip-zstd](https://github.com/mcmilk/7-Zip-zstd)
If you want to run our test-suite or scan a zstd compressed file, We recommend installing this [7-zip-zstd](https://github.com/mcmilk/7-Zip-zstd)
fork of 7zip. We are currently using `7z` for extracting `jar`, `apk`, `msi`, `exe` and `rpm` files.
> **Note**: The CVE Binary Tool cannot scan password-protected ZIP files. If you attempt to scan such a file, an error message will be logged regarding the failure in extraction..

Expand Down Expand Up @@ -537,7 +537,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
python 3.10+ 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
Expand Down Expand Up @@ -1165,7 +1165,7 @@ haxx,curl,7.34.0,CVE-2014-0139,MEDIUM,Unexplored,
haxx , curl , 7.34.0 , CVE-2014-0015, MEDIUM
"
style="width:100%;white-space:pre;">
<figcaption>formated console output</figcaption>
<figcaption>formatted console output</figcaption>
</figure>

4. `--format html` - creates a report in html format according to the specified HTML theme.
Expand Down Expand Up @@ -1197,7 +1197,7 @@ haxx,curl,7.34.0,CVE-2014-0139,MEDIUM,Unexplored,
libjpeg-turbo, 2.0.1, 2
ssh-ssh2, 2.0, 1"
style="width:100%;white-space:pre;">
<figcaption>formated HTML report</figcaption>
<figcaption>formatted HTML report</figcaption>
</figure>

The unexplored and new CVEs will be highlighted, it will look something like this:
Expand All @@ -1214,7 +1214,7 @@ The unexplored and new CVEs will be highlighted, it will look something like thi
sun, sunos, 5.4, 127,
ssh, ssh2, 2.0, 4"
style="width:100%;white-space:pre;">
<figcaption>formated HTML report</figcaption>
<figcaption>formatted HTML report</figcaption>
</figure>

You can also filter scanned products by remark:
Expand All @@ -1230,7 +1230,7 @@ You can also filter scanned products by remark:
mit, kerberos, 1.15.1, 3,
"
style="width:100%;white-space:pre;">
<figcaption>formated HTML report</figcaption>
<figcaption>formatted HTML report</figcaption>
</figure>

5. `--format pdf` - creates a report in PDF format.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
url="https://github.com/intel/cve-bin-tool",
license="GPL-3.0-or-later",
keywords=["security", "tools", "CVE"],
python_requires=">=3.8",
python_requires=">=3.10",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
Expand Down
8 changes: 4 additions & 4 deletions test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.11), run the tests, and deactivate:

```console
source venv3.8/bin/activate
source venv3.11/bin/activate
pytest
deactivate

Expand Down
4 changes: 2 additions & 2 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
CVE-bin-tool util tests
"""
import inspect
from typing import DefaultDict
from collections import defaultdict

from cve_bin_tool.cve_scanner import CVEScanner
from cve_bin_tool.util import CVEData, ProductInfo, inpath
Expand Down Expand Up @@ -35,7 +35,7 @@ def test_cve_scanner(self):

instance_attrs = vars(CVEScanner)["__annotations__"]
assert (
instance_attrs["all_cve_data"] == DefaultDict[ProductInfo, CVEData]
instance_attrs["all_cve_data"] == defaultdict[ProductInfo, CVEData]
), "Type of all_cve_data has been changed. Make sure it isn't breaking OutputEngine!"


Expand Down