Skip to content
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

cli: allow ignoring specific vulnerability IDs #275

Merged
merged 3 commits into from May 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
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
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
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 ' '}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty hard to read. IMO it's okay if we render , ignored 0 by default, but cc @di for opinions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, if , ignored 0 is ok then I'm all up for it

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
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
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
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