# dep-scan: dependency risk auditing with reachability, VDR and CSAF VEX output

> OWASP dep-scan audits a project's dependencies against known vulnerabilities, advisories and licence limits, and it goes further than a version-match list by computing whether the vulnerable code is actually reachable. It is built for CI, it runs its scanning locally, and it can emit SBOM, VDR and CSAF VEX documents.

**owasp-dep-scan/dep-scan** — OWASP dep-scan is a next-generation security and risk audit tool based on known vulnerabilities, advisories, and license limitations for project dependencies. Both local repositories and container images are supported as the input, and the tool is ideal for integration.

- Repository: https://github.com/owasp-dep-scan/dep-scan
- Website: https://depscan.readthedocs.io
- Stars: 1,329 · Forks: 141
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/owasp-dep-scan-dep-scan

## The problem it takes on, and what a version list cannot tell you

OWASP dep-scan is described as a next-generation security and risk audit tool based on known vulnerabilities, advisories and license limitations for project dependencies. Both local repositories and container images are supported as the input, and the tool is described as ideal for integration with ASPM and VM platforms and in CI environments.

The interesting part is the last clause of the feature list rather than the first. Scanning a local repository or a container image for known CVEs is a solved problem, and there are many tools that do it. What dep-scan adds is the question of whether the vulnerability is reachable.

The README explains this directly. A vulnerable package whose code is actually on an executed path is marked Reachable, while a package that is merely declared in the BOM but never called is not. Every slicer feeds the same shared, purl-keyed reachability engine, so the behaviour is uniform across languages. Reachability is on by default under --profile research, which is what dep-scan uses when reachability is on, and SemanticReachability additionally attributes reached services, endpoints and post-build, meaning binary or container, reachability.

That is the difference between a report nobody can act on and a report a team can act on. A typical dependency scan of a large application produces hundreds of findings, most of them in libraries that are present in the tree but not called from the vulnerable path. Reachability is the filter that reduces that to the set you actually have to look at, and it is the reason the tool is described as next-generation rather than as another version matcher.

The licence dimension is the other half of the scope, and it is not a bolt-on. The tool audits for license limitations alongside vulnerabilities, and the topics list includes compliance, sbom, sca, vex and supply-chain-security, which tells you the intended deployment is an ASPM pipeline rather than a laptop utility.

The project is MIT licensed, the primary language is Python, and the release history is steady, with v6.3.0 on 2026-07-23 following v6.2.0 in May and v6.1.0 in January, and the last push on 2026-10-14.

## Four slicers, seven ecosystems, one reachability engine

The reachability matrix in the README is the clearest statement of the design, and it is worth taking apart.

dep-scan computes reachability for seven language ecosystems through four slicers, and the matrix assigns one to each group.

The JVM, JavaScript and TypeScript, Python and PHP ecosystems all share atom, from the AppThreat organisation. That four-to-one mapping is a deliberate architectural choice. Rather than writing four separate reachability engines, one engine handles the majority of the languages, which is possible because the analysis is done at the call-graph level and the frameworks differ but the shape of the problem does not.

Rust uses rusi and Go uses golem, both from the cdxgen-plugins-bin repository, and .NET, covering C#, VB, F# and R, uses dosai from the owasp-dep-scan organisation. The mechanism differs slightly for each. For Rust, cdxgen runs rusi and persists its report, which dep-scan converts into the shared pipeline, so a crate that is actually called, for example time::now() for RUSTSEC-2020-0071, is Reachable while one merely present is not. For Go, dep-scan invokes golem directly for Go projects, which needs go on PATH, and marks a module Reachable when its functions are on a traced path. For .NET, cdxgen runs dosai, which emits an explicit per-package reachability verdict, a PackageReachability with a ReachabilityKind and a Confidence, and dep-scan treats that verdict as authoritative, so a NuGet package actually called, for example JsonConvert.DeserializeObject on a controlled input, is Reachable.

The Confidence field is worth noticing, because it means the .NET path does not just answer yes or no. It tells you how sure the slicer is, which is a different and more useful question than whether a symbol appears in the source.

The key property is stated in the README and it is the one to remember: every slicer feeds the same shared, purl-keyed reachability engine, so the behaviour is uniform across languages. A finding that says Reachable means the same thing whether it came from atom for a Python project or dosai for a .NET one. That uniformity is what makes the output usable as a policy input rather than as a set of per-language reports to be interpreted separately.

## Running it, and the four ways to run it

The quick start section gives several entry points, and choosing between them is mostly a question of what your environment already has.

Scanning a project locally in the Python version, with the research profile that turns reachability on:

```bash
depscan -i ./my-rust-app -o ./reports --profile research
```

The output directory is given with -o, and the profile selects the behaviour. For Go projects, the type is set with -t:

```bash
depscan -i ./my-go-app -o ./reports -t go
```

and for .NET:

```bash
depscan -i ./my-dotnet-app -o ./reports -t dotnet
```

The README also documents Homebrew for macOS and Linux, standalone binaries that require no Python or Node.js, scanning containers locally, scanning projects locally through a Docker container, and a server mode. The server mode is the one that matters for an ASPM platform, and the docker-compose.yml in the repository shows the intended shape of it, with a cdxgen service on port 9090 running the SBOM generator in server mode and a depscan service on port 7070 started with --server --server-host 0.0.0.0 --server-port 7070 and pointed at the cdxgen server with --cdxgen-server http://cdxgen:9090, and the two linked so depscan resolves cdxgen by name.

The API key is an environment variable, DEPSCAN_SERVER_API_KEY, with a default of dev-only-change-me in the compose file, and VDB_HOME is set to /app/vdb to point at the local vulnerability database. That last detail matters: it is the difference between a server that has the advisory data and one that has to fetch it, which is a meaningful operational difference for an air-gapped installation.

The package also exposes three console scripts rather than one. depscan is the main scanner at depscan.cli:main, depscan-vdb manages the vulnerability database at depscan.vdb_cli:main, and depscan-validate at depscan.validate_cli:main, which is the piece you would use to check a VEX or advisory document rather than to produce one. Separating database management from scanning matters in CI, where the database is often refreshed on a schedule and the scan runs on every build.

## Output formats, and the compliance documents it produces

The feature list describes four kinds of output, and they serve different audiences.

The first is the Software Bill of Materials with Vulnerability Disclosure Report information. The SBOM badge in the README credits cdxgen for generating the bill of materials, and the project structure shows the split: cdxgen is the SBOM generator, dep-scan consumes its output and adds the vulnerability and licence analysis on top. The composition is visible in the package dependencies, where ds-xbom-lib, ds-analysis-lib and ds-reporting-lib do the three jobs, and appthreat-vulnerability-db with the oras extra brings the advisory data.

The second is the CSAF 2.0 and 2.1 VEX document, with a CSAF_README.md in the contrib directory. A VEX document is the machine-readable way to say that a specific product is not affected by a specific advisory, or that the impact is not severe, so that downstream consumers of your SBOM do not have to re-derive the same conclusion. The VEX guide in the documentation covers the compliance story.

The third is the deep packages risk audit, for dependency confusion attacks and maintenance risks, with the risk audit docs in the advanced usage section of the documentation. Dependency confusion is a specific supply-chain attack where a public package with a name your build resolves ahead of your private one, and a tool that flags packages whose provenance does not match where you expect it is doing something a plain vulnerability scan does not.

The fourth is the vulnerability data sources themselves, which the README lists as its own section alongside Linux distros. That section is about where the advisory data comes from, and the existence of depscan-vdb as a separate command is the operational face of it.

The rest of the repository supports all this. There is a documentation/ directory, a contrib/ directory, a test/ directory, a packages/ directory, a build/ directory and a vendor/ directory, with a vendor-data.sh script at the top level. There is also a Dockerfile.al9 alongside the main Dockerfile, a docker-compose.yml at the root, a devenv.yaml and devenv.nix with a devenv.lock for the development environment, and a .devcontainer.json. The main Dockerfile is a heavy one, built from ghcr.io/almalinux/10-minimal:10 with Java 26, Maven 3.9.16, Gradle 9.6.1, Python 3.12 and Go 1.26.5 all installed, and the labels carrying the OWASP Foundation as maintainer and MIT as the licence. The reason is structural: the reachability slicers are separate executables in other languages, and the image has to carry the toolchains to run them.

## What dep-scan does not do, and where it stops

There are three limits, and the first is a scope boundary rather than a defect.

dep-scan audits dependencies. It does not scan your own source code for vulnerabilities, and it does not replace a static application security testing tool or a secret scanner. The topics list includes dependency-analysis, dependency-audit and vulnerability-scanners, and the inputs it accepts are repositories, container images, Kubernetes manifests and the OS. If the finding you need is in your own code rather than in a library you depend on, this tool does not produce it.

The second limit is that reachability is a static approximation, and the tool is careful about it in ways that matter. The per-language guides in the documentation cover binary resolution, backend and network safety, and restore requirements, and the Go section gives a concrete instruction to use --go-analyzer-network offline for untrusted repositories with a warm module cache. A reachability engine that traces call paths may need to resolve dependencies, and resolving them can mean running the project's own build tooling or fetching from the network, and running untrusted build scripts is a risk the tool has to manage rather than ignore. The offline mode is the answer to that, and it comes with a cost, since a cold module cache means the analysis has less to work with.

The third limit is the coverage of the reachability matrix itself. Seven ecosystems are supported, and everything outside them gets a version-matched vulnerability list with no reachability verdict. The topics list also includes cve, which is the generic identifier, and the tool is a dependency audit rather than a container image vulnerability scanner, so scanning an image finds the vulnerabilities in the packages inside it rather than the vulnerabilities in the operating system layers, except where the Linux distros data source covers them.

There is one more thing to weigh, and it is about the trust model. The project ships an AI-DECLARATION.md and a SKILL.md at the top of the repository, and a THREAT_MODEL.md. The presence of a threat model document in a security tool is a good sign, and the AI declaration is a disclosure that some tooling-assisted contribution has happened. For a tool whose output is used to decide what to fix, the provenance of the reachability verdict matters, and the fact that the .NET path treats the dosai verdict as authoritative is a design decision that puts a lot of weight on one component.

## dep-scan against the other dependency scanners

The comparison worth making is not between dep-scan and another tool, it is between dep-scan and the version-match scanner you probably already have.

A conventional software composition analysis tool produces a list of components with known advisories, usually sorted by severity score. That output is a filter, not a decision, and the reason it becomes noise is structural. A large application depends on hundreds of packages, most of which are transitively pulled in, and the vulnerable function in a vulnerable library is very often not the one your code calls. The severity score says nothing about reachability, so the reviewer has to open each finding and work it out by hand, and after a few hundred findings they stop, and the tool becomes a compliance artefact rather than a working practice.

The approach dep-scan takes is to compute the call path and let the result speak. A vulnerable package that is never called is not a finding, and the README's example is precise about it: a crate that is actually called, time::now() for RUSTSEC-2020-0071, is Reachable, while one merely present is not. That is the difference between a list you triage and a list you act on.

The second axis is deployment. The README's stated integration target is ASPM and VM platforms and CI environments, and the server mode plus the compose file with a separate cdxgen service and a local VDB_HOME shows a design aimed at a shared scanning service rather than a per-developer command. That is a different posture from the desktop scanners, which are per-laptop and per-invocation.

The third axis is what the tool refuses to do. It does not attempt to be a secret scanner, a static analyser of your code, or a container hardening tool. It audits dependencies and their licences, and it does that with more resolution than a version match because the reachability engine is the product rather than a filter bolted onto one.

The practical cost of that resolution is real and worth stating: you need the toolchains for the languages you use, the analysis is slower than a version match, and the reachability verdict for a given ecosystem is only as good as its slicer. If your estate is one language with a well-supported slicer, the trade is good. If it is a long tail of languages, the reachability advantage shrinks to whatever the tool can do generically, and at that point the simpler scanner may serve you better.

## Conclusion

Adopt dep-scan if you run software composition analysis in CI and your current tool produces a list of vulnerable packages that nobody can triage, because the reachability analysis is what turns that list into a prioritised one. Do not adopt it as a replacement for a scanner that covers a language ecosystem it does not support, and read the safety notes before pointing it at an untrusted repository. Verify first that your dependency graph is generated with the purl identifiers the tool keys on, that the vulnerability database is current for the sources you rely on, and that the profile you select in CI is the one whose reachability behaviour you have read about.

## FAQ

### What does reachability mean in OWASP dep-scan?

A vulnerable package whose code is actually on an executed path is marked Reachable, while a package that is merely declared in the BOM but never called is not. Every slicer feeds the same shared, purl-keyed reachability engine, and reachability is on by default under `--profile research`.

### Which languages does dep-scan support reachability analysis for?

Seven ecosystems through four slicers. Java and the JVM, JavaScript and TypeScript, Python and PHP share atom; Rust uses rusi and Go uses golem, both from cdxgen-plugins-bin; .NET covering C#, VB, F# and R uses dosai.

### How do I run a dep-scan scan?

The README shows `depscan -i ./my-rust-app -o ./reports --profile research` for a local project, and the same form with `-t go` or `-t dotnet` for those types. You can also install it through Homebrew, use standalone binaries, or run it in server mode, which the docker-compose.yml wires to a cdxgen server on port 9090 with depscan on 7070.

### What compliance documents can dep-scan produce?

It generates a Software Bill of Materials with Vulnerability Disclosure Report information, and a Common Security Advisory Framework CSAF 2.0 or 2.1 VEX document, with details in contrib/CSAF_README.md. The package also installs a separate depscan-vdb command for the vulnerability database and depscan-validate for validation.

## Sources

- [License: MIT](https://github.com/owasp-dep-scan/dep-scan/blob/master/LICENSE)
- [owasp-dep-scan/dep-scan on GitHub](https://github.com/owasp-dep-scan/dep-scan)
- [Project website](https://depscan.readthedocs.io)
- [README](https://github.com/owasp-dep-scan/dep-scan/blob/master/README.md)
- [Releases](https://github.com/owasp-dep-scan/dep-scan/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/owasp-dep-scan-dep-scan
