Skip to content

Commit 897c630

Browse files
Adding --osv-url argument to allow use of private OSV vulnerability services (#810)
* Adding --osv-url argument to allow use of private OSV vulnerability services #805 * cleanup, refactoring, add CHANGELOG --------- Co-authored-by: William Woodruff <[email protected]> Co-authored-by: William Woodruff <[email protected]>
1 parent 89ba959 commit 897c630

File tree

7 files changed

+51
-26
lines changed

7 files changed

+51
-26
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ __pycache__/
66
html/
77
dist/
88
.python-version
9+
/.pytest_cache/

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,18 @@ All versions prior to 0.0.9 are untracked.
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
* `pip-audit` now supports the `--osv-url URL` flag, which can be used to
14+
retrieve vulnerabilities from a custom OSV service. This is useful for
15+
organizations that host their own mirror of the OSV database, or that
16+
have custom OSV records
17+
([#810](https://github.com/pypa/pip-audit/pull/810))
18+
1119
## [2.9.0]
1220

21+
### Added
22+
1323
* `pip-audit` now supports [PEP 751](https://peps.python.org/pep-0751/)
1424
lockfiles. These lockfiles can be audited in "project" mode by
1525
passing `--locked` to `pip-audit`

README.md

+9-7
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,13 @@ python -m pip_audit --help
132132
<!-- @begin-pip-audit-help@ -->
133133
```
134134
usage: pip-audit [-h] [-V] [-l] [-r REQUIREMENT] [--locked] [-f FORMAT]
135-
[-s SERVICE] [-d] [-S] [--desc [{on,off,auto}]]
136-
[--aliases [{on,off,auto}]] [--cache-dir CACHE_DIR]
137-
[--progress-spinner {on,off}] [--timeout TIMEOUT]
138-
[--path PATH] [-v] [--fix] [--require-hashes]
139-
[--index-url INDEX_URL] [--extra-index-url URL]
140-
[--skip-editable] [--no-deps] [-o FILE] [--ignore-vuln ID]
141-
[--disable-pip]
135+
[-s SERVICE] [--osv-url OSV_URL] [-d] [-S]
136+
[--desc [{on,off,auto}]] [--aliases [{on,off,auto}]]
137+
[--cache-dir CACHE_DIR] [--progress-spinner {on,off}]
138+
[--timeout TIMEOUT] [--path PATH] [-v] [--fix]
139+
[--require-hashes] [--index-url INDEX_URL]
140+
[--extra-index-url URL] [--skip-editable] [--no-deps]
141+
[-o FILE] [--ignore-vuln ID] [--disable-pip]
142142
[project_path]
143143

144144
audit the Python environment for dependencies with known vulnerabilities
@@ -165,6 +165,8 @@ optional arguments:
165165
-s SERVICE, --vulnerability-service SERVICE
166166
the vulnerability service to audit dependencies
167167
against (choices: osv, pypi) (default: pypi)
168+
--osv-url OSV_URL URL to use for the OSV API instead of the default
169+
(default: https://api.osv.dev/v1/query)
168170
-d, --dry-run without `--fix`: collect all dependencies but do not
169171
perform the auditing step; with `--fix`: perform the
170172
auditing step but do not perform any fixes (default:

pip_audit/_cli.py

+18-11
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
MarkdownFormat,
3333
VulnerabilityFormat,
3434
)
35-
from pip_audit._service import OsvService, PyPIService, VulnerabilityService
35+
from pip_audit._service import OsvService, PyPIService
3636
from pip_audit._service.interface import ConnectionError as VulnServiceConnectionError
37-
from pip_audit._service.interface import ResolvedDependency, SkippedDependency
37+
from pip_audit._service.interface import ResolvedDependency, SkippedDependency, VulnerabilityService
3838
from pip_audit._state import AuditSpinner, AuditState
3939
from pip_audit._util import assert_never
4040

@@ -100,14 +100,6 @@ class VulnerabilityServiceChoice(str, enum.Enum):
100100
Osv = "osv"
101101
Pypi = "pypi"
102102

103-
def to_service(self, timeout: int, cache_dir: Path | None) -> VulnerabilityService:
104-
if self is VulnerabilityServiceChoice.Osv:
105-
return OsvService(cache_dir, timeout)
106-
elif self is VulnerabilityServiceChoice.Pypi:
107-
return PyPIService(cache_dir, timeout)
108-
else:
109-
assert_never(self) # pragma: no cover
110-
111103
def __str__(self) -> str:
112104
return self.value
113105

@@ -249,6 +241,14 @@ def _parser() -> argparse.ArgumentParser: # pragma: no cover
249241
VulnerabilityServiceChoice,
250242
),
251243
)
244+
parser.add_argument(
245+
"--osv-url",
246+
type=str,
247+
metavar="OSV_URL",
248+
dest="osv_url",
249+
default=os.environ.get("PIP_AUDIT_OSV_URL", OsvService.DEFAULT_OSV_URL),
250+
help="URL to use for the OSV API instead of the default",
251+
)
252252
parser.add_argument(
253253
"-d",
254254
"--dry-run",
@@ -438,7 +438,14 @@ def audit() -> None: # pragma: no cover
438438
parser = _parser()
439439
args = _parse_args(parser)
440440

441-
service = args.vulnerability_service.to_service(args.timeout, args.cache_dir)
441+
service: VulnerabilityService
442+
if args.vulnerability_service is VulnerabilityServiceChoice.Osv:
443+
service = OsvService(cache_dir=args.cache_dir, timeout=args.timeout, osv_url=args.osv_url)
444+
elif args.vulnerability_service is VulnerabilityServiceChoice.Pypi:
445+
service = PyPIService(cache_dir=args.cache_dir, timeout=args.timeout)
446+
else:
447+
assert_never(args.vulnerability_service) # pragma: no cover
448+
442449
output_desc = args.desc.to_bool(args.format)
443450
output_aliases = args.aliases.to_bool(args.format)
444451
formatter = args.format.to_format(output_desc, output_aliases)

pip_audit/_service/osv.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ class OsvService(VulnerabilityService):
3131
package vulnerability information.
3232
"""
3333

34-
def __init__(self, cache_dir: Path | None = None, timeout: int | None = None):
34+
DEFAULT_OSV_URL = "https://api.osv.dev/v1/query"
35+
36+
def __init__(
37+
self,
38+
cache_dir: Path | None = None,
39+
timeout: int | None = None,
40+
osv_url: str = DEFAULT_OSV_URL,
41+
):
3542
"""
3643
Create a new `OsvService`.
3744
@@ -43,6 +50,7 @@ def __init__(self, cache_dir: Path | None = None, timeout: int | None = None):
4350
"""
4451
self.session = caching_session(cache_dir, use_pip=False)
4552
self.timeout = timeout
53+
self.osv_url = osv_url
4654

4755
def query(self, spec: Dependency) -> tuple[Dependency, list[VulnerabilityResult]]:
4856
"""
@@ -54,14 +62,13 @@ def query(self, spec: Dependency) -> tuple[Dependency, list[VulnerabilityResult]
5462
return spec, []
5563
spec = cast(ResolvedDependency, spec)
5664

57-
url = "https://api.osv.dev/v1/query"
5865
query = {
5966
"package": {"name": spec.canonical_name, "ecosystem": "PyPI"},
6067
"version": str(spec.version),
6168
}
6269
try:
6370
response: requests.Response = self.session.post(
64-
url=url,
71+
url=self.osv_url,
6572
data=json.dumps(query),
6673
timeout=self.timeout,
6774
)

pip_audit/_service/pypi.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ class PyPIService(VulnerabilityService):
3232
package vulnerability information.
3333
"""
3434

35-
def __init__(self, cache_dir: Path | None = None, timeout: int | None = None) -> None:
35+
def __init__(
36+
self, cache_dir: Path | None = None, timeout: int | None = None, **kwargs: dict
37+
) -> None:
3638
"""
3739
Create a new `PyPIService`.
3840

test/test_cli.py

-4
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ def test_str(self):
2727

2828

2929
class TestVulnerabilityServiceChoice:
30-
def test_to_service_is_exhaustive(self, cache_dir):
31-
for choice in VulnerabilityServiceChoice:
32-
assert choice.to_service(0, cache_dir) is not None
33-
3430
def test_str(self):
3531
for choice in VulnerabilityServiceChoice:
3632
assert str(choice) == choice.value

0 commit comments

Comments
 (0)