diff --git a/.gitignore b/.gitignore index 4d279d60..6f8d82e5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ __pycache__/ html/ dist/ .python-version +*.pyc diff --git a/README.md b/README.md index 9cde478d..b3d0d5ca 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,10 @@ optional arguments: --ignore-vuln ID ignore a specific vulnerability by its vulnerability ID; this option can be used multiple times (default: []) + --ignore-vuln-ultimatum ID end-date + ignore a specific vulnerability by its vulnerability + ID up to the end-date; this option can be used multiple times (default: + []) --disable-pip don't use `pip` for dependency resolution; this can only be used with hashed requirements files or if the `--no-deps` flag has been provided (default: False) @@ -367,6 +371,9 @@ for your particular application or use case, you can use the `--ignore-vuln ID` option to ignore specific vulnerability reports. `--ignore-vuln` supports aliases, so you can use a `GHSA-xxx` or `CVE-xxx` ID instead of a `PYSEC-xxx` ID if the report in question does not have a PYSEC ID. +To prevent vulnerabilities being ignored ad infinitum in an automated context, +an ultimatum can be set for the ignore using +`--ignore-vuln-ultimatum ID end-date`. For example, here is how you might ignore GHSA-w596-4wvx-j9j6, which is a common source of noisy vulnerability reports and false positives for users of @@ -384,8 +391,9 @@ requirements-style inputs, alternative vulnerability feeds, and so forth. It can also be passed multiple times, to ignore multiple reports: ```console -# Run the audit as normal, but exclude any reports that match these IDs -$ pip-audit --ignore-vuln CVE-XXX-YYYY --ignore-vuln CVE-ZZZ-AAAA +# Run the audit as normal, but exclude any reports that match these IDs, +with an ultimatum for the second +$ pip-audit --ignore-vuln CVE-XXX-YYYY --ignore-vuln-ultimatum CVE-ZZZ-AAAA 2023-09-26 ``` ### `pip-audit` takes longer than I expect! diff --git a/pip_audit/_cli.py b/pip_audit/_cli.py index 8b6b8e83..859df786 100644 --- a/pip_audit/_cli.py +++ b/pip_audit/_cli.py @@ -10,8 +10,9 @@ import os import sys from contextlib import ExitStack, contextmanager +from datetime import datetime from pathlib import Path -from typing import IO, Iterator, NoReturn, cast +from typing import IO, Iterator, NoReturn, cast, List, Union, Tuple from pip_audit import __version__ from pip_audit._audit import AuditOptions, Auditor @@ -44,6 +45,8 @@ package_logger = logging.getLogger("pip_audit") package_logger.setLevel(os.environ.get("PIP_AUDIT_LOGLEVEL", "INFO").upper()) +DATE_SEP = "::" + @contextmanager def _output_io(name: Path) -> Iterator[IO[str]]: # pragma: no cover @@ -327,6 +330,20 @@ def _parser() -> argparse.ArgumentParser: # pragma: no cover "this option can be used multiple times" ), ) + parser.add_argument( + "--ignore-vuln-ultimatum", + nargs=2, + type=str, + metavar=("ID", "end date"), + action="append", + dest="ignore_vulns_ultimatum", + default=[], + help=( + "ignore a specific vulnerability by its vulnerability ID " + "with a given end date; " + "this option can be used multiple times" + ), + ) parser.add_argument( "--disable-pip", action="store_true", @@ -458,7 +475,7 @@ def audit() -> None: # pragma: no cover vuln_count = 0 skip_count = 0 vuln_ignore_count = 0 - vulns_to_ignore = set(args.ignore_vulns) + vulns_to_ignore = set(args.ignore_vulns) | ultimatum_vulns_to_ignore(args.ignore_vulns_ultimatum) try: for spec, vulns in auditor.audit(source): if spec.is_skipped(): @@ -557,3 +574,10 @@ def audit() -> None: # pragma: no cover if skip_count > 0 or formatter.is_manifest: with _output_io(args.output) as io: print(formatter.format(result, fixes), file=io) + + +def ultimatum_vulns_to_ignore(ignore_vulns: List[Tuple[str, str]]): + return set( + v for v, enddate_str in ignore_vulns + if datetime.now() < datetime.strptime(enddate_str, "%Y-%m-%d") + ) diff --git a/test/test_cli.py b/test/test_cli.py index 5bf577bd..710dab8b 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -66,6 +66,8 @@ def test_str(self): (["--fix"], 2, 2, "fixed 2 vulnerabilities in 2 packages"), ([], 0, 0, "No known vulnerabilities found"), (["--ignore-vuln", "bar"], 0, 1, "No known vulnerabilities found, 1 ignored"), + (["--ignore-vuln-ultimatum", "baz", "1970-01-01"], 1, 1, "Found 2 known vulnerabilities in 1 package"), + (["--ignore-vuln-ultimatum", "baz", "9999-01-01"], 0, 1, "No known vulnerabilities found, 1 ignored"), ], ) def test_plurals(capsys, monkeypatch, args, vuln_count, pkg_count, expected): @@ -90,7 +92,10 @@ def test_plurals(capsys, monkeypatch, args, vuln_count, pkg_count, expected): ] if "--ignore-vuln" in args: - result[0][1].append(pretend.stub(id="bar", aliases=set(), has_any_id=lambda x: True)) + result[0][1].append(pretend.stub(id="bar", aliases=set(), has_any_id=lambda x: "bar" in x, fix_versions="baz")) + + if "--ignore-vuln-ultimatum" in args: + result[0][1].append(pretend.stub(id="baz", aliases=set(), has_any_id=lambda x: "baz" in x, fix_versions="baz")) auditor = pretend.stub(audit=lambda a: result) monkeypatch.setattr(pip_audit._cli, "Auditor", lambda *a, **kw: auditor)