VDB
Sign up
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. The representation is not source text, but it still reveals dependency/API names, argument positions, graph edges, and coarse dataflow facts.

SentNever sent
Call sites: module, API name, argument positionFunction bodies, control flow
Whether each argument is tainted, and howIdentifiers — user_id becomes v3
Literal shapesSTR<len:9, [a-z0-9-]>Literal values, comments, docstrings
Your lockfile — package names, versions, graph edgesFile paths, project name

You do not have to take that on faith. --emit-irprints 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. Credit is all-or-nothing on an exact match with the wrapper VDB issued, so deleting the internal-range check earns nothing for the two that remain:

-> OPEN (no-sanitizer-at-boundary)

A scheme check with the internal ranges left open is the exact shape of a working SSRF. Partial credit is how an attestation that calls it closed gets minted, so there is no partial credit. Apply a wrapper meant for a different sink and the reason names every defense the path still requires.

VDB binds the evidence to the originally issued path, the original and fixed IR fingerprints, and the graph, and credits a wrapper only when its AST exactly matches the reviewed implementation. The public signature verifier proves that VDB issued the decision over the submitted abstraction; it cannot prove that the abstraction matches a deployed binary.

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
POST https://vdb.ai.kr/v1/harden/evidence/verify