depaudit
Know exactly what third-party code ships in your projects. depaudit parses lockfiles across Python, JavaScript, Go and Rust, builds the dependency graph, enforces a license policy, checks an offline OSV advisory snapshot with ecosystem-correct version semantics, flags hygiene problems (duplicates, loose pins, typosquats) and emits CycloneDX 1.5 and SPDX 2.3 SBOMs - as a single CLI that is easy to run in CI and has no runtime network requirement.
Features
- Lockfile parsers (written from scratch):
requirements.txt(hashes, markers, extras,-r, pip-compile# via),poetry.lock,uv.lock,Pipfile.lock,pdm.lock,package-lock.jsonv2/v3,yarn.lockv1 and berry,pnpm-lock.yaml,go.mod+go.sum,Cargo.lock. Monorepos are scanned recursively and merged. - Dependency graph: direct vs transitive, prod vs dev, and
depaudit why <pkg>for every path from a root. - License detection: installed
METADATA/package.json, lockfile fields, LICENSE file text classified against phrase fingerprints of ~30 SPDX licenses, full SPDX expression parsing (MIT OR Apache-2.0,GPL-2.0-only WITH Classpath-exception-2.0), optional--onlinelookup on PyPI / npm with an on-disk cache. - Policy (
depaudit.toml): allowed / denied / review licenses, copyleft rules for SaaS vs distributed software, package allow / deny lists, banned versions, maximum age since last release, vulnerability threshold, CI exit codes. - Vulnerabilities: offline OSV snapshots (zip, directory or single file) matched with PEP 440, semver and Go pseudo-version semantics; fixed versions and a minimal upgrade suggestion.
- Hygiene: same package at several versions, unpinned / loose specifiers, packages without a license, typosquat candidates (edit distance 1 from the top-1000 PyPI / npm names).
- SBOM: CycloneDX 1.5 and SPDX 2.3 JSON with purls, hashes from the lockfile and dependency relationships, validated against the bundled official JSON schemas.
- Output: rich tables, JSON, Markdown;
depaudit diff base.json head.jsonshows the new dependencies, licenses and vulnerabilities a pull request introduces.
Install
Python 3.12+.
cd depaudit
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/depaudit --help
Quickstart
The repository ships a realistic polyglot monorepo in examples/monorepo (11 lockfiles, one of each
supported kind), an OSV snapshot and an offline metadata snapshot.
cd depaudit
M=examples/monorepo
O="--metadata examples/metadata.json --site-packages $M/site-packages"
.venv/bin/depaudit scan $M $O # inventory
.venv/bin/depaudit audit $M $O --osv examples/osv # policy + vulnerabilities + hygiene (exit 1 on violations)
.venv/bin/depaudit why semver $M # why is this here?
.venv/bin/depaudit sbom $M $O --format cyclonedx -o sbom.cdx.json
.venv/bin/depaudit sbom $M $O --format spdx -o sbom.spdx.json
.venv/bin/depaudit diff examples/pr-diff/base.json examples/pr-diff/head.json
On your own project: depaudit audit . --osv path/to/osv-export.zip (the policy is read from ./depaudit.toml if present).
Usage
All commands take an optional PATH (a directory, scanned recursively, or a single lockfile; default .). The commands
that read packages share these options:
| Option | Purpose |
|---|---|
--policy FILE |
policy file (default: depaudit.toml next to PATH) |
--metadata FILE |
offline metadata snapshot (license, latest version and release date per package); repeatable |
--site-packages DIR |
extra site-packages directory to read license metadata from; repeatable. node_modules and .venv/lib/python*/site-packages next to a lockfile are found automatically |
--online |
fill missing license / latest-release data from PyPI and the npm registry |
--cache-dir, --cache-ttl |
cache for --online (default .depaudit-cache, 24 h) |
scan and licenses
$ depaudit scan examples/monorepo --metadata examples/metadata.json --site-packages examples/monorepo/site-packages
monorepo: 91 packages (41 direct, 50 transitive, 18 dev) from 13 lockfile(s)
Ecosystem Package Version Scope License
──────────────────────────────────────────────────────────────────────────────────────────────────
cargo aho-corasick 0.7.18 transitive prod Unlicense OR MIT
cargo autocfg 1.1.0 transitive prod Apache-2.0 OR MIT
cargo chrono 0.4.19 direct prod MIT OR Apache-2.0
cargo diff 0.1.13 transitive dev MIT OR Apache-2.0
cargo libc 0.2.147 transitive prod MIT OR Apache-2.0
cargo memchr 2.5.0 transitive prod Unlicense OR MIT
cargo num-integer 0.1.45 transitive prod MIT OR Apache-2.0
cargo num-traits 0.2.15 transitive prod MIT OR Apache-2.0
cargo pretty_assertions 1.4.0 direct dev MIT OR Apache-2.0
cargo regex 1.5.4 direct prod MIT OR Apache-2.0
cargo regex-syntax 0.6.25 transitive prod MIT OR Apache-2.0
cargo time 0.1.45 transitive prod MIT OR Apache-2.0
cargo yansi 0.5.1 transitive dev MIT OR Apache-2.0
golang github.com/gin-gonic v1.9.0 direct prod MIT
/gin
golang github.com/pkg/error v0.9.1 direct prod BSD-2-Clause
s
golang github.com/stretchr/ v1.8.4 direct prod MIT
...
$ depaudit licenses examples/monorepo --metadata examples/metadata.json --site-packages examples/monorepo/site-packages
License Packages Examples
──────────────────────────────────────────────────────────────────────────────────────────────────
MIT 46 github.com/gin-gonic/gin,
github.com/stretchr/testify, @babel/runtime,
@types/node, ansi-styles ...
BSD-3-Clause 11 golang.org/x/crypto, golang.org/x/net,
highlight.js, click, colorama ...
MIT OR Apache-2.0 10 chrono, diff, libc, num-integer, num-traits
...
Apache-2.0 4 typescript, requests, requests, tzdata
ISC 4 lru-cache, semver, semver, yallist
BSD-2-Clause 2 github.com/pkg/errors, pygments
MPL-2.0 2 certifi, pathspec
Unlicense OR MIT 2 aho-corasick, memchr
(unknown) 1 reqests
Apache Software License; BSD License 1 packaging
Apache-2.0 OR MIT 1 autocfg
BSD-3-Clause OR Apache-2.0 1 python-dateutil
BSD-like 1 idna
GPL-3.0-only 1 chalkk
HPND 1 pillow
LGPL-2.0-or-later 1 chardet
LGPL-2.1-only 1 chardet
WTFPL 1 left-pad
scan --format json prints the same data as JSON. Note how idna shows the raw string BSD-like (not an SPDX id), and
python-dateutil shows a dual-license pair read from trove classifiers.
audit
$ depaudit audit examples/monorepo --metadata examples/metadata.json \
--site-packages examples/monorepo/site-packages --osv examples/osv --as-of 2024-06-01
monorepo: 91 packages (41 direct, 50 transitive) from 13 lockfile(s), as of 2024-06-01
Findings
Severity Rule Detail
──────────────────────────────────────────────────────────────────────────────────────────────────
error banned-version lodash 4.17.15 matches banned range <4.17.21: prototype pollution
and command injection (fixed in 4.17.21)
error banned-version pyyaml 5.3 matches banned range <5.4: arbitrary code execution via
full_load
error license-policy chalkk 4.0.0: GPL-3.0-only is strong copyleft; policy for
distributed distribution: deny
error package-denied left-pad is on the package deny list (npm:left-pad)
error typosquat chalkk is one edit away from popular package chalk
error typosquat reqests is one edit away from popular package requests
...
Advisory Package Severity Fixed in Upgrade to
──────────────────────────────────────────────────────────────────────────────────────────────────
GHSA-m5pq-gvj9-9vr8 regex 1.5.4 high 1.5.5 1.5.5
GHSA-wcg3-cvx6-7396 time 0.1.45 medium 0.2.23 0.2.23
GHSA-3vp4-m3rf-835h github.com/gin-goni medium v1.9.1 v1.9.1
c/gin v1.9.0
GHSA-ffhg-7mh4-33c4 golang.org/x/crypto high v0.0.0-202002201836 v0.0.0-202002201836
v0.0.0-201903082217 23-bac4c82f6975 23-bac4c82f6975
18-c2843e01d9a2
GHSA-vvpx-j8f3-3w6h golang.org/x/net high v0.17.0 v0.17.0
v0.7.0
GHSA-grv7-fg5c-xmjg braces 3.0.2 high 3.0.3 3.0.3
GHSA-35jh-r3h4-6jhm lodash 4.17.15 high 4.17.21 4.17.21
GHSA-p6mc-m468-83gw lodash 4.17.15 high 4.17.19 4.17.21
GHSA-952p-6rrq-rcjv micromatch 4.0.5 medium 4.0.8 4.0.8
GHSA-xvch-5gv4-984h minimist 1.2.5 critical 1.2.6 1.2.6
GHSA-c2qf-rxjj-qqgw semver 6.3.0 medium 6.3.1 6.3.1
GHSA-c2qf-rxjj-qqgw semver 7.3.5 medium 7.5.2 7.5.2
GHSA-3h5v-q93c-6h6q ws 8.13.0 high 8.17.1 8.17.1
GHSA-h5c8-rqwp-cp95 jinja2 2.11.2 medium 3.1.3 3.1.3 (major)
GHSA-8ghj-p4vj-mr35 pillow 9.5.0 medium - 10.0.1 (major)
GHSA-j7hp-h8jx-5ppr pillow 9.5.0 high 10.0.1 10.0.1 (major)
GHSA-8q59-q68h-6hv4 pyyaml 5.3 critical 5.4 5.4
GHSA-j8r2-6x86-q33q requests 2.19.1 medium 2.31.0 2.31.0
PYSEC-2018-28 requests 2.19.1 high 2.20.0 2.31.0
GHSA-v845-jxx5-vc9f urllib3 1.24.1 medium 1.26.17 1.26.17
GHSA-v845-jxx5-vc9f urllib3 1.26.16 medium 1.26.17 1.26.17
FAIL: 17 error(s), 31 warning(s), 21 vulnerability match(es)
(exit code 1). Options: --format table|json|markdown, -o FILE, --checks license,packages,vuln,hygiene,age,outdated,
--as-of YYYY-MM-DD (reproducible age checks), --strict (warnings fail too), -v (also list informational findings
such as outdated packages).
The JSON report (--format json) is the input of diff; the Markdown report is meant for PR comments and CI summaries.
Exit codes: 0 clean, 1 violations, 2 bad input or usage.
Vulnerability rows show the fixed version of the matching advisory range and the minimal upgrade that clears every
advisory in the snapshot for that package ((major) marks a major-version change). pillow 9.5.0 illustrates why: the
first advisory has no fixed version (last_affected: 9.5.0), the second is fixed in 10.0.1, and 10.0.1 is the smallest
version affected by neither.
why
$ depaudit why braces examples/monorepo
npm:braces 3.0.2 (transitive prod)
docs-site -> micromatch 4.0.5 -> braces 3.0.2
$ depaudit why semver examples/monorepo
npm:semver 6.3.0 (transitive prod)
storefront-web -> make-dir 3.1.0 -> semver 6.3.0
npm:semver 7.3.5 (direct prod)
storefront-web -> semver 7.3.5
$ depaudit why time examples/monorepo
cargo:time 0.1.45 (transitive prod)
fx-cli -> chrono 0.4.19 -> time 0.1.45
$ depaudit why golang.org/x/net examples/monorepo
golang:golang.org/x/net v0.7.0 (transitive prod)
no dependency path recorded (the lockfile does not store the dependency graph)
PACKAGE can be name, name@version or ecosystem:name. Go lockfiles do not store a graph, so indirect modules have
no path.
sbom
depaudit sbom examples/monorepo --format cyclonedx -o sbom.cdx.json # CycloneDX 1.5
depaudit sbom examples/monorepo --format spdx -o sbom.spdx.json # SPDX 2.3
Documents are validated against the official schemas before they are written (--no-validate to skip). An excerpt
(one component, first two dependency entries):
{
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:536683b4-c6d9-5baa-a10a-d92c2586ed6d",
"version": 1,
"metadata": {
"timestamp": "2026-10-16T15:18:25Z",
"tools": {
"components": [
{
"type": "application",
"name": "depaudit",
"version": "0.1.0"
}
]
},
"component": {
"type": "application",
"bom-ref": "root:monorepo",
"name": "monorepo"
}
},
"components": [
{
"type": "library",
"bom-ref": "pkg:npm/minimist@1.2.5",
"name": "minimist",
"version": "1.2.5",
"purl": "pkg:npm/minimist@1.2.5",
"hashes": [
{
"alg": "SHA-512",
"content": "9229c3253193c4387e12c15ef05719aec4b022d4015c41b18e6ff5f5c8193b4d21bd25446367fd1c3751092cda1cc01bbc9c0b227f18664c90d1012b7e57f9b1"
}
],
...
diff
$ P="--policy examples/monorepo/depaudit.toml --osv examples/osv --format json"
$ depaudit audit examples/pr-diff/base $P -o base.json # on the base branch
$ depaudit audit examples/pr-diff/head $P -o head.json # on the PR branch
$ depaudit diff base.json head.json
New dependencies
Package Versions License Direct
──────────────────────────────────────────────────
npm:csv-tools 2.3.0 GPL-3.0-only yes
npm:minimist 1.2.5 MIT no
New licenses in the tree: GPL-3.0-only
New vulnerabilities
Advisory Package Severity Upgrade to
──────────────────────────────────────────────────────────────
GHSA-xvch-5gv4-984h minimist 1.2.5 critical 1.2.6
New findings
Severity Rule Detail
──────────────────────────────────────────────────────────────────────────────────────────────────
error license-policy csv-tools 2.3.0: GPL-3.0-only is strong copyleft; policy for
distributed distribution: deny
REGRESSION: change introduces new vulnerabilities or policy errors
Exit code 1 means the change introduces a vulnerability or a new policy error. --format markdown produces a PR
comment (see examples/pr-diff).
Configuration
A minimal depaudit.toml:
distribution = "distributed" # or "saas"
[licenses]
allowed = ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC"]
denied = ["AGPL-3.0-only"]
review = ["MPL-2.0"]
[[banned_versions]]
package = "npm:lodash"
versions = "<4.17.21"
[age]
max_days = 730
[vulnerabilities]
fail_on = "high"
The full reference (decision order, copyleft categories, expressions, every key) is in docs/policy.md.
Architecture
src/depaudit/
cli.py click commands, option wiring, exit codes
parsers/ one module per ecosystem: python.py, js.py, golang.py, rust.py, common.py; __init__ = discovery
model.py Package, Inventory (graph, roots, scopes), purls
graph.py why-paths
versions.py dispatch; pep440.py and semver.py implement the version semantics
spdx_expr.py SPDX expression parser / evaluator
licdata.py phrase fingerprints (no license texts); licenses.py classifier + normalisation
localmeta.py license detection from site-packages / node_modules
metadata.py offline snapshots + PyPI / npm lookup with disk cache; enrich.py merges all sources
policy.py depaudit.toml, license verdicts, package / banned-version / age checks
osv.py, cvss.py OSV snapshot loading, range matching, upgrade suggestion, CVSS v3 scoring
hygiene.py duplicates, loose specifiers, typosquats (data/popular.json)
audit.py runs the checks, builds the Report
report.py, diff.py table / Markdown rendering, report diff
sbom.py CycloneDX / SPDX generation and schema validation (schemas/)
Deeper write-ups: docs/design.md (graph, license detection, classifier worked example, SBOM mapping), docs/version-semantics.md (PEP 440, semver, Go, OSV ranges), docs/policy.md.
Tests
.venv/bin/pytest -q
The suite covers every lockfile parser, the graph and why paths, PEP 440 / semver / Cargo / Go version ordering and
range matching (table-driven, including pre-releases, epochs and local versions), SPDX parsing and evaluation, the
license classifier, policy outcomes, the OSV matcher and upgrade suggestions, typosquats, schema validity of both SBOM
formats (including negative cases), diff, the registry client (with an injected fetcher, never the network) and the
CLI end to end.
Limitations
- Vulnerability data comes only from the OSV snapshot you provide; the sample in
examples/osvis a small hand-trimmed set of public advisories for the demo, not a substitute for a real export. GIT-type OSV ranges are not evaluated. Version-less sources (git/URL requirements) are reported as loose, not resolved.- Go lockfiles carry no dependency graph, and
go.sum-only scans approximate the build list by taking the highest version of each module. - Requirement markers are recorded but not evaluated: every pinned requirement is inventoried regardless of platform.
- License classification is phrase-based: files that bundle several licenses are reported as their best single match,
and the ambiguous
BSD License/Apache Software Licensetrove classifiers are assumed to mean BSD-3-Clause and Apache-2.0. Verify unusual cases by hand. - Typosquat detection is a heuristic against popular names (PyPI, npm); it will flag legitimate near-names and cannot catch squats of packages outside the embedded lists. Scoped npm names are skipped.
--onlineuses the PyPI and npm registries only (not crates.io or the Go proxy).- npm
package-lock.jsonv1 is not supported.
License
MIT, see LICENSE.