-
-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathpip-audit-bulk
More file actions
executable file
·128 lines (99 loc) · 3.91 KB
/
Copy pathpip-audit-bulk
File metadata and controls
executable file
·128 lines (99 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python
# pip-audit-bulk: run pip-audit in bulk, saving (reduced) reports in JSON
import collections
import concurrent.futures
import json
import os
import shutil
import subprocess
import sys
from itertools import islice
from pathlib import Path
from tempfile import NamedTemporaryFile
# This entire script is path-sensitive.
_HERE = Path(__file__).resolve().parent
_SITE = _HERE / "site"
assert _SITE.is_dir(), "missing site dir (wrong dir?)"
# formula-name -> [requirements]
_REQUIREMENTS_JSON = _SITE / "formula-requirements.json"
_REQUIREMENTS = json.loads(_REQUIREMENTS_JSON.read_text())
_AUDITS_JSON = _SITE / "formula-audits.json"
audits = {
"vulnerable": {},
"failures": [],
"skipped": [], # NOTE: currently unused
}
if "CI" in os.environ:
_SUMMARY = Path(os.environ["GITHUB_STEP_SUMMARY"])
else:
_SUMMARY = None
_QUICK_RUN = os.environ.get("QUICK_RUN", "false") == "true"
_JOBS = 4
# {(dependency, CVE): [list of formula]}
_SUMMARY_RESULTS = collections.defaultdict(list)
assert shutil.which("osv-scanner"), "missing osv-scanner"
def _audit_formula(formula, requirements):
# osv-scanner wants a requirements.txt file, so we make a temporary one.
with NamedTemporaryFile(mode="w") as req_file:
req_file.write("\n".join(requirements))
req_file.flush()
return formula, subprocess.run(
[
"osv-scanner",
"--lockfile",
f"requirements.txt:{req_file.name}",
"--format=json",
"--config=osv-scanner-config.toml",
],
capture_output=True,
text=True,
)
_items = _REQUIREMENTS.items()
if _QUICK_RUN:
_items = islice(_items, 10)
with concurrent.futures.ThreadPoolExecutor(max_workers=_JOBS) as pool:
futures = [pool.submit(_audit_formula, f, r) for f, r in _items]
n_total = len(futures)
width = len(str(n_total))
for n_complete, fut in enumerate(concurrent.futures.as_completed(futures), start=1):
formula, result = fut.result()
prefix = f"[{n_complete:>{width}}/{n_total}]"
# If we don't see anything on stdout, osv-scanner probably failed.
# Log it as an error.
if not result.stdout:
print(f"\t{prefix} >:( osv-scanner failed: {result.stderr}", file=sys.stderr)
audits["failures"].append(formula)
continue
audit = json.loads(result.stdout)
results = audit["results"]
# If we're left with anything, dump it as a result.
if results:
seen_packages = {}
for r in results:
for p in r["packages"]:
seen_packages.setdefault(
(p["package"]["name"], p["package"]["version"]), p
)
vulnerable_packages = list(seen_packages.values())
vuln_count = 0
for p in vulnerable_packages:
vuln_count += len(p["groups"])
for v in p["groups"]:
_SUMMARY_RESULTS[(p["package"]["name"], v["ids"][0])].append(formula)
print(f"\t{prefix} :-( found {vuln_count} vulnerabilities in {formula}", file=sys.stderr)
audits["vulnerable"][formula] = vulnerable_packages
else:
print(f"\t{prefix} :-) no vulnerabilities found in {formula}", file=sys.stderr)
if _QUICK_RUN:
print("\t⏱ quick run, skipped remaining formulae", file=sys.stderr)
_AUDITS_JSON.write_text(json.dumps(audits, indent=2))
render = """Identified the following vulnerabilities:
| Dependency | Vulnerability | Formula |
| ---------- | ------------- | ------- |
"""
for (dep, vuln), formulas in sorted(_SUMMARY_RESULTS.items()):
formulas_rendered = ", ".join(f"`{f}`" for f in formulas)
render += f"| `{dep}` | [`{vuln}`](https://osv.dev/vulnerability/{vuln}) | {formulas_rendered} |\n"
if _SUMMARY:
with _SUMMARY.open(mode="a") as io:
print(render, file=io)