VDB
Beta · API access

Risk that has no CVE yet

Every other dependency scanner starts from a published advisory. This one starts from your call site and asks a different question: can attacker-controlled data reach a dangerous operation somewhere down the transitive chain — whether or not anyone has written it up?

What it returns

path P-8daff1f4-net_request   confidence 0.85   no CVE required
  callsite  app.py:14  requests.get(v2)
  through   requests@2.32.3 -> urllib3@2.2.2
  sink      urllib3.connection.request  (net_request)
  fix       wrap the argument at the call site: vdb_safe_url(...)
  residual  install-script-malware, background-exfiltration  (not covered)

The fix is applied at your call site. VDB never asks you to patch, fork, or pin the dependency — you usually cannot fix someone else's package, and you can always fix your own boundary.

Your source code never leaves your machine

The file is abstracted locally into a call-graph representation. Only that representation and your lockfile are uploaded.

Sent Never sent
Call sites: module, API name, argument position Function bodies, control flow
Whether each argument is tainted, and how Identifiers — user_id becomes v3
Literal shapesSTR<len:9, [a-z0-9-]> Literal values, comments, docstrings
Your lockfile — already public data File paths, project name

You do not have to take that on faith. --emit-ir prints exactly what would be sent and makes no network call at all.

How a path is decided

  1. 1
    Summaries, built once per package version. Each one records where an argument reaches a dangerous operation inside that package, and where it is handed to a specific argument of a specific downstream API. Built offline from the real registry artifact by parsing only — package code is never imported and never executed.
  2. 2
    Composition over your resolved graph. Those summaries are chained across the exact versions your lockfile resolves to. A version range cannot be analyzed — the answer differs per version.
  3. 3
    Six sink classes. File path operations, command execution, network requests, deserialization, regex evaluation, and global-state mutation.
When a package can't be summarized

Dynamic dispatch, native extensions, a failed signature check. Rather than dropping the path and under-reporting silently, the analysis assumes the argument passes through and prices that assumption into a confidence score you can see. A conservative guess you can inspect beats a silent miss.

Proving you closed it

Finding a path is half of it. After you apply the fix, re-analysis issues signed evidence bound to the hash of the dependency graph it was decided against — so an attestation cannot be quietly reused after your graph moves.

Partial defense is not closure. A network sink requires a scheme allowlist, a host allowlist, and internal-range blocking. Apply two of the three and you get told which one is missing, not a passing grade:

-> OPEN (sanitizer-incomplete:missing=block-internal-ranges)

A scheme check with the internal ranges left open is the exact shape of a working SSRF. An attestation that calls it closed would be worse than no attestation at all.

Turning a finding list into a triage order

A scan on a real project returns hundreds of advisories, nearly all of them about code the application never puts attacker-controlled data into. A queue nobody can work through is a queue everybody ends up ignoring. Point the same analysis at your source tree and it says which ones can be reached:

$ vdb harden ./src --manifest uv.lock --vex

36 advisory finding(s) in uv.lock
  not_affected        26
  under_investigation 10

  analyzed 41 file(s) · ecosystems: pypi

The output is an OpenVEX document, so any tool that reads the format can subtract the same set — and it gets a URL, because the result is only useful when a team looks at the same list.

Point it at the tree, not a file. A not_affected claim is only as wide as the code behind it: derive "nothing reaches urllib3" from one file while forty other modules import it and the document is wrong, in a format built to be trusted automatically. Below two analyzed files, every such claim is withheld.

It never says affected. Reaching a package is not reaching the function an advisory names, and stating otherwise would be a guess wearing the clothes of a finding. Reachability comes from static summaries, so a relation the analyzer missed is a false negative — the document says so on itself. Evidence for triage order, not proof of non-exploitability.

What it does not cover

This ships with every answer, not as a footnote. These are never marked defended, whatever fix you apply:

  • Install-script malware. Runs before your code ever calls the package.
  • Background exfiltration. Happens outside your input path, so a call-site boundary never sees it.
  • Unanalyzed dynamic loading. Reflection and dynamic imports cannot be resolved statically.

A boundary fix constrains what your argument can do. It does not make a dependency trustworthy.

Getting access

Python and PyPI today, over uv.lock, poetry.lock, Pipfile.lock, requirements.txt, and CycloneDX. The API is live for signed-in users; the local abstraction step ships with the client.

POST https://vdb.ai.kr/v1/harden/analyze
POST https://vdb.ai.kr/v1/harden/verify