Skip to content

Commit

Permalink
Add aliases to output. (#721)
Browse files Browse the repository at this point in the history
* Added aliases to JSON output.

* Added aliases to column and markdown formats.

* Added --aliases flag to documentation and included examples.

* Allow alias IDs to be toggled for JSON output; updated documentation.

* Fix `--aliases` flag description to match README

---------

Co-authored-by: pboulos <[email protected]>
Co-authored-by: Alex Cameron <[email protected]>
  • Loading branch information
3 people authored Jan 10, 2024
1 parent c2f4bd7 commit 11d2786
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 86 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ All versions prior to 0.0.9 are untracked.

## [Unreleased]

### Added

* `pip-audit` now includes vulnerability aliases when `--format=json` is used,
and also includes them in other output formats if specified by adding the
flag `--aliases`

## [2.6.3]

### Fixed
Expand Down
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ python -m pip_audit --help
<!-- @begin-pip-audit-help@ -->
```
usage: pip-audit [-h] [-V] [-l] [-r REQUIREMENT] [-f FORMAT] [-s SERVICE] [-d]
[-S] [--desc [{on,off,auto}]] [--cache-dir CACHE_DIR]
[--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]
[--disable-pip]
[-S] [--desc [{on,off,auto}]] [--aliases [{on,off,auto}]]
[--cache-dir CACHE_DIR] [--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] [--disable-pip]
[project_path]

audit the Python environment for dependencies with known vulnerabilities
Expand Down Expand Up @@ -171,6 +171,11 @@ optional arguments:
defaults to `on` for the `json` format. This flag has
no effect on the `cyclonedx-json` or `cyclonedx-xml`
formats. (default: auto)
--aliases [{on,off,auto}]
includes alias IDs for each vulnerability; `auto`
defaults to `on` for the `json` format. This flag has
no effect on the `cyclonedx-json` or `cyclonedx-xml`
formats. (default: auto)
--cache-dir CACHE_DIR
the directory to use as an HTTP cache for PyPI; uses
the `pip` HTTP cache by default (default: None)
Expand Down Expand Up @@ -274,6 +279,16 @@ Flask 0.5 PYSEC-2019-179 1.0
Flask 0.5 PYSEC-2018-66 0.12.3
```
Audit dependencies including aliases:
```
$ pip-audit --aliases
Found 2 known vulnerabilities in 1 package
Name Version ID Fix Versions Aliases
---- ------- -------------- ------------ -------------------------------------
Flask 0.5 PYSEC-2019-179 1.0 CVE-2019-1010083, GHSA-5wv5-4vpf-pj6m
Flask 0.5 PYSEC-2018-66 0.12.3 CVE-2018-1000656, GHSA-562c-5r94-xh97
```
Audit dependencies including descriptions:
```
$ pip-audit --desc
Expand All @@ -298,13 +313,21 @@ Found 2 known vulnerabilities in 1 package
"fix_versions": [
"1.0"
],
"aliases": [
"CVE-2019-1010083",
"GHSA-5wv5-4vpf-pj6m"
],
"description": "The Pallets Project Flask before 1.0 is affected by: unexpected memory usage. The impact is: denial of service. The attack vector is: crafted encoded JSON data. The fixed version is: 1. NOTE: this may overlap CVE-2018-1000656."
},
{
"id": "PYSEC-2018-66",
"fix_versions": [
"0.12.3"
],
"aliases": [
"CVE-2018-1000656",
"GHSA-562c-5r94-xh97"
],
"description": "The Pallets Project flask version Before 0.12.3 contains a CWE-20: Improper Input Validation vulnerability in flask that can result in Large amount of memory usage possibly leading to denial of service. This attack appear to be exploitable via Attacker provides JSON data in incorrect encoding. This vulnerability appears to have been fixed in 0.12.3. NOTE: this may overlap CVE-2019-1010083."
}
]
Expand Down
46 changes: 41 additions & 5 deletions pip_audit/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,17 @@ class OutputFormatChoice(str, enum.Enum):
CycloneDxXml = "cyclonedx-xml"
Markdown = "markdown"

def to_format(self, output_desc: bool) -> VulnerabilityFormat:
def to_format(self, output_desc: bool, output_aliases: bool) -> VulnerabilityFormat:
if self is OutputFormatChoice.Columns:
return ColumnsFormat(output_desc)
return ColumnsFormat(output_desc, output_aliases)
elif self is OutputFormatChoice.Json:
return JsonFormat(output_desc)
return JsonFormat(output_desc, output_aliases)
elif self is OutputFormatChoice.CycloneDxJson:
return CycloneDxFormat(inner_format=CycloneDxFormat.InnerFormat.Json)
elif self is OutputFormatChoice.CycloneDxXml:
return CycloneDxFormat(inner_format=CycloneDxFormat.InnerFormat.Xml)
elif self is OutputFormatChoice.Markdown:
return MarkdownFormat(output_desc)
return MarkdownFormat(output_desc, output_aliases)
else:
assert_never(self) # pragma: no cover

Expand Down Expand Up @@ -134,6 +134,30 @@ def __str__(self) -> str:
return self.value


@enum.unique
class VulnerabilityAliasChoice(str, enum.Enum):
"""
Whether or not vulnerability aliases should be added to the `pip-audit` output.
"""

On = "on"
Off = "off"
Auto = "auto"

def to_bool(self, format_: OutputFormatChoice) -> bool:
if self is VulnerabilityAliasChoice.On:
return True
elif self is VulnerabilityAliasChoice.Off:
return False
elif self is VulnerabilityAliasChoice.Auto:
return bool(format_ is OutputFormatChoice.Json)
else:
assert_never(self) # pragma: no cover

def __str__(self) -> str:
return self.value


@enum.unique
class ProgressSpinnerChoice(str, enum.Enum):
"""
Expand Down Expand Up @@ -241,6 +265,17 @@ def _parser() -> argparse.ArgumentParser: # pragma: no cover
"`auto` defaults to `on` for the `json` format. This flag has no "
"effect on the `cyclonedx-json` or `cyclonedx-xml` formats.",
)
parser.add_argument(
"--aliases",
type=VulnerabilityAliasChoice,
choices=VulnerabilityAliasChoice,
nargs="?",
const=VulnerabilityAliasChoice.On,
default=VulnerabilityAliasChoice.Auto,
help="includes alias IDs for each vulnerability; "
"`auto` defaults to `on` for the `json` format. This flag has no "
"effect on the `cyclonedx-json` or `cyclonedx-xml` formats.",
)
parser.add_argument(
"--cache-dir",
type=Path,
Expand Down Expand Up @@ -385,7 +420,8 @@ def audit() -> None: # pragma: no cover

service = args.vulnerability_service.to_service(args.timeout, args.cache_dir)
output_desc = args.desc.to_bool(args.format)
formatter = args.format.to_format(output_desc)
output_aliases = args.aliases.to_bool(args.format)
formatter = args.format.to_format(output_desc, output_aliases)

# Check for flags that are only valid with requirements files
if args.requirements is None:
Expand Down
10 changes: 9 additions & 1 deletion pip_audit/_format/columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ class ColumnsFormat(VulnerabilityFormat):
columns.
"""

def __init__(self, output_desc: bool):
def __init__(self, output_desc: bool, output_aliases: bool):
"""
Create a new `ColumnFormat`.
`output_desc` is a flag to determine whether descriptions for each vulnerability should be
included in the output as they can be quite long and make the output difficult to read.
`output_aliases` is a flag to determine whether aliases (such as CVEs) for each
vulnerability should be included in the output.
"""
self.output_desc = output_desc
self.output_aliases = output_aliases

@property
def is_manifest(self) -> bool:
Expand All @@ -64,6 +68,8 @@ def format(
header = ["Name", "Version", "ID", "Fix Versions"]
if fixes:
header.append("Applied Fix")
if self.output_aliases:
header.append("Aliases")
if self.output_desc:
header.append("Description")
vuln_data.append(header)
Expand Down Expand Up @@ -131,6 +137,8 @@ def _format_vuln(
]
if applied_fix is not None:
vuln_data.append(self._format_applied_fix(applied_fix))
if self.output_aliases:
vuln_data.append(", ".join(vuln.aliases))
if self.output_desc:
vuln_data.append(vuln.description)
return vuln_data
Expand Down
8 changes: 7 additions & 1 deletion pip_audit/_format/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@ class JsonFormat(VulnerabilityFormat):
JSON objects.
"""

def __init__(self, output_desc: bool):
def __init__(self, output_desc: bool, output_aliases: bool):
"""
Create a new `JsonFormat`.
`output_desc` is a flag to determine whether descriptions for each vulnerability should be
included in the output as they can be quite long and make the output difficult to read.
`output_aliases` is a flag to determine whether aliases (such as CVEs) for each
vulnerability should be included in the output.
"""
self.output_desc = output_desc
self.output_aliases = output_aliases

@property
def is_manifest(self) -> bool:
Expand Down Expand Up @@ -78,6 +82,8 @@ def _format_vuln(self, vuln: service.VulnerabilityResult) -> dict[str, Any]:
"id": vuln.id,
"fix_versions": [str(version) for version in vuln.fix_versions],
}
if self.output_aliases:
vuln_json["aliases"] = list(vuln.aliases)
if self.output_desc:
vuln_json["description"] = vuln.description
return vuln_json
Expand Down
11 changes: 10 additions & 1 deletion pip_audit/_format/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,18 @@ class MarkdownFormat(VulnerabilityFormat):
Markdown tables.
"""

def __init__(self, output_desc: bool) -> None:
def __init__(self, output_desc: bool, output_aliases: bool) -> None:
"""
Create a new `MarkdownFormat`.
`output_desc` is a flag to determine whether descriptions for each vulnerability should be
included in the output as they can be quite long and make the output difficult to read.
`output_aliases` is a flag to determine whether aliases (such as CVEs) for each
vulnerability should be included in the output.
"""
self.output_desc = output_desc
self.output_aliases = output_aliases

@property
def is_manifest(self) -> bool:
Expand Down Expand Up @@ -66,6 +70,9 @@ def _format_vuln_results(
if fixes:
header += " | Applied Fix"
border += " | ---"
if self.output_aliases:
header += " | Aliases"
border += " | ---"
if self.output_desc:
header += " | Description"
border += " | ---"
Expand Down Expand Up @@ -101,6 +108,8 @@ def _format_vuln(
)
if applied_fix is not None:
vuln_text += f" | {self._format_applied_fix(applied_fix)}"
if self.output_aliases:
vuln_text += f" | {', '.join(vuln.aliases)}"
if self.output_desc:
vuln_text += f" | {vuln.description}"
return vuln_text
Expand Down
2 changes: 1 addition & 1 deletion test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def query(self, spec):
VulnerabilityResult(
id="fake-id",
description="this is not a real result",
fix_versions=[fixed],
aliases=set(),
fix_versions=[fixed],
)
]

Expand Down
8 changes: 4 additions & 4 deletions test/format/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@
Version("1.1"),
Version("1.4"),
],
aliases=set(),
aliases={"CVE-0000-00000"},
),
service.VulnerabilityResult(
id="VULN-1",
description="The second vulnerability",
fix_versions=[Version("1.0")],
aliases=set(),
aliases={"CVE-0000-00001"},
),
],
_RESOLVED_DEP_BAR: [
service.VulnerabilityResult(
id="VULN-2",
description="The third vulnerability",
fix_versions=[],
aliases=set(),
aliases={"CVE-0000-00002"},
)
],
}
Expand All @@ -47,7 +47,7 @@
Version("1.1"),
Version("1.4"),
],
aliases=set(),
aliases={"CVE-0000-00000"},
),
],
_SKIPPED_DEP: [],
Expand Down
Loading

0 comments on commit 11d2786

Please sign in to comment.