Skip to content

Commit

Permalink
cli: allow ignoring specific vulnerability IDs (#275)
Browse files Browse the repository at this point in the history
* pip_audit: provide has_any_id API for VulnerabilityResult

This API allows developers to query whether a VulnerabilityResult
contains at least one of the vulnerability IDs passed in as argument
either as its ID or as one of the aliases.

This method will return True if the vulnerability IDs passed in as
argument intersect with the VulnerabilityResult's aliases + ID, False
otherwise.

* cli: allow ignoring specific vulnerability IDs

This commit introduces a new flag to pip-audit's CLI (--ignore-vuln)
that takes a string representing a vulnerability id, if any of the
packages scanned detect said vulnerability, it will be ignored in the
final report.

This allows users of pip-audit to ignore certain vulnerabilities that
they may deem not exploitable for whatever reason.

The flag can be used multiple times to ignore multiple vulnerabilities.

Closes #245

Co-authored-by: William Woodruff <william@trailofbits.com>
  • Loading branch information
Elkasitu and woodruffw authored May 12, 2022
1 parent 0a407fa commit 1e03403
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 3 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

All versions prior to 0.0.9 are untracked.

## [Unreleased]

### Added

* CLI: The `--ignore-vuln` option has been added, allowing users to
specify vulnerability IDs to ignore during the final report.
([#275](https://github.com/trailofbits/pip-audit/pull/275))

## [2.2.1] - 2022-05-02

### Fixed
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ usage: pip-audit [-h] [-V] [-l] [-r REQUIREMENTS] [-f FORMAT] [-s SERVICE]
[--path PATHS] [-v] [--fix] [--require-hashes]
[--index-url INDEX_URL] [--extra-index-url EXTRA_INDEX_URLS]
[--skip-editable] [--no-deps] [-o FILE]
[--ignore-vuln IGNORE_VULNS]
[project_path]
audit the Python environment for dependencies with known vulnerabilities
Expand Down Expand Up @@ -142,6 +143,9 @@ optional arguments:
False)
-o FILE, --output FILE
output results to the given file (default: None)
--ignore-vuln IGNORE_VULNS
ignore a specific vulnerability by its vulnerability
ID (default: [])
```
<!-- @end-pip-audit-help@ -->

Expand Down
26 changes: 24 additions & 2 deletions pip_audit/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,14 @@ def _parser() -> argparse.ArgumentParser:
# argparse's default renderer uses __repr__ and produces
# a pretty unpleasant help message.
)
parser.add_argument(
"--ignore-vuln",
type=str,
action="append",
dest="ignore_vulns",
default=[],
help="ignore a specific vulnerability by its vulnerability ID",
)
return parser


Expand Down Expand Up @@ -390,6 +398,8 @@ def audit() -> None:
pkg_count = 0
vuln_count = 0
skip_count = 0
vuln_ignore_count = 0
vulns_to_ignore = set(args.ignore_vulns)
for (spec, vulns) in auditor.audit(source):
if spec.is_skipped():
spec = cast(SkippedDependency, spec)
Expand All @@ -401,6 +411,10 @@ def audit() -> None:
else:
spec = cast(ResolvedDependency, spec)
state.update_state(f"Auditing {spec.name} ({spec.version})")
if vulns_to_ignore:
filtered_vulns = [v for v in vulns if not v.has_any_id(vulns_to_ignore)]
vuln_ignore_count += len(vulns) - len(filtered_vulns)
vulns = filtered_vulns
result[spec] = vulns
if len(vulns) > 0:
pkg_count += 1
Expand Down Expand Up @@ -442,7 +456,8 @@ def audit() -> None:
if vuln_count > 0:
summary_msg = (
f"Found {vuln_count} known "
f"{'vulnerability' if vuln_count == 1 else 'vulnerabilities'} "
f"{'vulnerability' if vuln_count == 1 else 'vulnerabilities'}"
f"{(vuln_ignore_count and ', ignored %d ' % vuln_ignore_count) or ' '}"
f"in {pkg_count} {'package' if pkg_count == 1 else 'packages'}"
)
if args.fix:
Expand All @@ -457,7 +472,14 @@ def audit() -> None:
if pkg_count != fixed_pkg_count:
sys.exit(1)
else:
print("No known vulnerabilities found", file=sys.stderr)
summary_msg = "No known vulnerabilities found"
if vuln_ignore_count:
summary_msg += f", {vuln_ignore_count} ignored"

print(
summary_msg,
file=sys.stderr,
)
# If our output format is a "manifest" format we always emit it,
# even if nothing other than a dependency summary is present.
if skip_count > 0 or formatter.is_manifest:
Expand Down
6 changes: 6 additions & 0 deletions pip_audit/_service/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ def merge_aliases(self, other: VulnerabilityResult) -> VulnerabilityResult:
self.id, self.description, self.fix_versions, self.aliases | other.aliases - {self.id}
)

def has_any_id(self, ids: Set[str]) -> bool:
"""
Returns whether ids intersects with {id} | aliases.
"""
return bool(ids & (self.aliases | {self.id}))


class VulnerabilityService(ABC):
"""
Expand Down
11 changes: 11 additions & 0 deletions test/service/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,14 @@ def test_vulnerability_result_update_aliases():
merged = result1.merge_aliases(result2)
assert merged.id == "FOO"
assert merged.aliases == {"BAR", "BAZ", "ZAP", "QUUX"}


def test_vulnerability_result_has_any_id():
result = VulnerabilityResult(
id="FOO", description="bar", fix_versions=[Version("1.0.0")], aliases={"BAR", "BAZ", "QUUX"}
)

assert result.has_any_id({"FOO"})
assert result.has_any_id({"ham", "eggs", "BAZ"})
assert not result.has_any_id({"zilch"})
assert not result.has_any_id(set())
9 changes: 8 additions & 1 deletion test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
([], 1, 1, "Found 1 known vulnerability in 1 package"),
([], 2, 1, "Found 2 known vulnerabilities in 1 package"),
([], 2, 2, "Found 2 known vulnerabilities in 2 packages"),
(["--ignore-vuln", "bar"], 2, 2, "Found 2 known vulnerabilities, ignored 1 in 2 packages"),
(["--fix"], 1, 1, "fixed 1 vulnerability in 1 package"),
(["--fix"], 2, 1, "fixed 2 vulnerabilities in 1 package"),
(["--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"),
],
)
def test_plurals(capsys, monkeypatch, args, vuln_count, pkg_count, expected):
Expand All @@ -30,11 +33,15 @@ def test_plurals(capsys, monkeypatch, args, vuln_count, pkg_count, expected):
canonical_name="something" + str(i),
version=1,
),
[pretend.stub(fix_versions=[2], id="foo")] * (vuln_count // pkg_count),
[pretend.stub(fix_versions=[2], id="foo", aliases=set(), has_any_id=lambda x: False)]
* (vuln_count // pkg_count),
)
for i in range(pkg_count)
]

if "--ignore-vuln" in args:
result[0][1].append(pretend.stub(id="bar", aliases=set(), has_any_id=lambda x: True))

auditor = pretend.stub(audit=lambda a: result)
monkeypatch.setattr(pip_audit._cli, "Auditor", lambda *a, **kw: auditor)

Expand Down

0 comments on commit 1e03403

Please sign in to comment.