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..3dd847b2 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ usage: pip-audit [-h] [-V] [-l] [-r REQUIREMENT] [-f FORMAT] [-s SERVICE] [-d] [--progress-spinner {on,off}] [--timeout TIMEOUT] [--path PATH] [-v] [--fix] [--require-hashes] [--index-url INDEX_URL] [--extra-index-url URL] - [--skip-editable] [--no-deps] [-o FILE] [--ignore-vuln ID] + [--skip-editable] [--no-deps] [-o FILE] [--ignore-vuln ID[::enddate]] [--disable-pip] [project_path] @@ -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 ID::enddate + temporarily ignore a specific vulnerability by its + vulnerability ID; 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) @@ -380,12 +384,14 @@ $ pip-audit --ignore-vuln GHSA-w596-4wvx-j9j6 The `--ignore-vuln ID` option works with all other dependency resolution and auditing options, meaning that it should function correctly with requirements-style inputs, alternative vulnerability feeds, and so forth. +Ignores can be time-limited by specifying `--ignore-vuln ID::enddate` to safeguard +against ignoring vulnerabilities too long. 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 +$ pip-audit --ignore-vuln CVE-XXX-YYYY --ignore-vuln 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..8216cdc2 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 @@ -458,7 +461,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(yield_vulns_to_ignore(args.ignore_vulns)) try: for spec, vulns in auditor.audit(source): if spec.is_skipped(): @@ -557,3 +560,16 @@ 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 yield_vulns_to_ignore(ignore_vulns: List[str]): + for vuln in ignore_vulns: + if DATE_SEP in vuln: + vuln_id, enddate_str = vuln.split(DATE_SEP) + enddate = datetime.strptime(enddate_str, "%Y-%m-%d") # should the format be customizable? + if datetime.now() < enddate: + yield vuln_id + else: + logger.warning(f"Vulnerability {vuln_id} not ignored as the expiry date {enddate_str} has passed.") + else: + yield vuln diff --git a/test/test_cli.py b/test/test_cli.py index 5bf577bd..888b6bfb 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", "bar::1970-01-01"], 1, 1, "Found 2 known vulnerabilities in 1 package"), + (["--ignore-vuln", "bar::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,8 @@ 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)) + should_be_found = "bar::1970-01-01" not in args + result[0][1].append(pretend.stub(id="bar", aliases=set(), has_any_id=lambda x: "bar" in x, fix_versions="baz")) auditor = pretend.stub(audit=lambda a: result) monkeypatch.setattr(pip_audit._cli, "Auditor", lambda *a, **kw: auditor)