# 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.json` v2/v3, `yarn.lock` v1 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 `--online` lookup 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.json` shows the new dependencies, licenses and
  vulnerabilities a pull request introduces.

## Install

Python 3.12+.

```bash
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`](examples/monorepo) (11 lockfiles, one of each
supported kind), an OSV snapshot and an offline metadata snapshot.

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

```text
$ 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                   
  ...
```

```text
$ 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`

```text
$ 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`

```text
$ 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`

```bash
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):

```json
{
  "$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`

```text
$ 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`:

```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](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](docs/design.md) (graph, license detection, classifier worked example, SBOM mapping),
[docs/version-semantics.md](docs/version-semantics.md) (PEP 440, semver, Go, OSV ranges), [docs/policy.md](docs/policy.md).

## Tests

```bash
.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/osv` is 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 License` trove 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.
- `--online` uses the PyPI and npm registries only (not crates.io or the Go proxy).
- npm `package-lock.json` v1 is not supported.

## License

MIT, see [LICENSE](LICENSE).
