One file, start to finish
Eighteen lines of ordinary Flask, a lockfile, and the whole loop: what VDB finds, what left the machine to find it, the fix, and the signed evidence that the path is closed. Every block below is copied from a real run — including the one that finds nothing.
Nothing exotic
A handler that proxies an avatar, a loader for an uploaded profile, and a health check. No CVE is involved in anything below: both packages are pinned at versions that do exactly what their documentation says.
import requests
import yaml
from flask import request
def fetch_avatar():
"""Proxy an avatar for the profile page."""
url = request.args["avatar_url"]
return requests.get(url).content
def load_profile(blob):
"""Read a profile document a user uploaded."""
return yaml.load(blob)
def health():
return requests.get("https://status.internal.example/health").json()The lockfile is what decides the answer — a version range cannot be analyzed, because the answer differs per version.
requests==2.19.0
urllib3==1.23
PyYAML==5.3.1
idna==2.7
chardet==3.0.4
certifi==2026.7.22Run it
vdb harden app.py --manifest requirements.txt4 dataflow path(s) decided (independently of whether any CVE exists)
* net_request confidence 0.8
path : requests@2.19.0 → urllib3@1.23 → urllib3@1.23
sink : urllib3@1.23 - urllib3.util.parse_url
fix : scheme-allowlist, host-allowlist, block-internal-ranges
residual (undefended) : install-script malware, background data exfiltration,
unanalyzed dynamic loading, dynamic dispatch on the path
path_id : P-4927330b4da0-net_request
* cmd_exec confidence 0.8
path : pyyaml@5.3.1 → pyyaml@5.3.1
sink : pyyaml@5.3.1 - yaml.load
fix : allowlist-check
residual (undefended) : install-script malware, background data exfiltration,
unanalyzed dynamic loading, dynamic dispatch on the path
path_id : P-4db57f067a93-cmd_exec
Print wrapper code with --patch, then re-check with --verify <path_id>.Two things worth reading twice. The network path runs through requests into urllib3 — the package the risk actually lands in is one nobody wrote down. And the fix line names three defenses, not one: a scheme check alone is not an SSRF fix.
What it did not flag: health()calls the same requests.getwith a literal URL and produces no path. That is the only line on this page that proves the tool is deciding rather than pattern-matching imports.
See what left the machine
--emit-ir prints the exact payload and makes no network call, so you can read it before you send anything. The file name is F0001, the variables are v1 andv2, and the source isEXT. No source text, no identifiers, no literal values, no paths.
{
"version": 1,
"language": "python",
"files": ["F0001"],
"sources": [
{ "symbol": "v1", "kind": "input" },
{ "symbol": "v2", "kind": "param" }
],
"flows": [
{ "from": "EXT", "to": "v1", "transform": "identity", "kind": "assign" }
],
"callsites": [
{
"id": "c1",
"module": "requests",
"api": "get",
"line": 9,
"args": [
{ "index": 0, "symbol": "v1", "tainted": true,
"transform": "identity", "sanitizers": [] }
]
}
]
}The abstraction still reveals dependency and API names, argument positions and graph edges. Treat those as project metadata when deciding whether to use the hosted service.
Take the fix
--patch prints a wrapper for your call site. Note what it is not: a patched, forked or pinned dependency. You usually cannot fix someone else's package; you can always fix your own boundary.
def vdb_safe_url(value, allowed_hosts=()):
"""Scheme/host allowlist plus internal-range blocking."""
import ipaddress, socket
from urllib.parse import urlparse
u = urlparse(str(value))
if u.scheme not in ("http", "https"):
raise ValueError("scheme not allowed")
if not allowed_hosts:
raise ValueError("a non-empty host allowlist is required")
if u.hostname not in allowed_hosts:
raise ValueError("host not in allowlist")
try:
addresses = {row[4][0] for row in socket.getaddrinfo(
u.hostname or "", u.port or (443 if u.scheme == "https" else 80),
type=socket.SOCK_STREAM)}
except (socket.gaierror, UnicodeError):
raise ValueError("host does not resolve")
for addr in addresses:
ip = ipaddress.ip_address(addr)
if (ip.is_private or ip.is_loopback or ip.is_link_local
or ip.is_reserved or ip.is_multicast or ip.is_unspecified):
raise ValueError("internal address blocked")
return value
# At the call site: wrap the argument (the dependency itself is never modified)
# get(vdb_safe_url(v1, allowed_hosts=("api.example.com",)), ...)Applied, the handler becomes one line longer:
def fetch_avatar():
url = request.args["avatar_url"]
return requests.get(vdb_safe_url(url, allowed_hosts=ALLOWED_HOSTS)).contentProve you closed it
vdb harden app.py --manifest requirements.txt \
--verify P-4927330b4da0-net_requestpath P-4927330b4da0-net_request -> CLOSED (sanitizer-applied:block-internal-ranges,host-allowlist,scheme-allowlist)
graph hash : 8e25c9dd6228dd7c...
decided at : 2026-08-28T03:14:48Z
signature : 541aee2e69f232e02d7cc4a7...
note : This authenticates VDB's decision over the submitted abstract IR.
It does not prove that the IR faithfully represents a deployed
build. When closure rests on a wrapper, only an exact
reviewed-wrapper AST is credited.The evidence is bound to the graph hash it was decided against, so an attestation cannot be quietly reused after a dependency moves. Anyone can check the signature at POST /v1/harden/evidence/verifywithout the signing key.
Edit the wrapper and it stops counting. Delete the internal-range loop, keep the scheme and host checks, and you do not get two thirds of a pass:
path P-4927330b4da0-net_request -> OPEN (no-sanitizer-at-boundary)Credit is all-or-nothing on an exact match with the reviewed implementation. A scheme check with the internal ranges left open is the exact shape of a working SSRF, and partial credit is how an attestation that calls it closed gets minted. Function names and the word allowlist are not accepted as proof either.
What this example does not show
- Closure is not proof of safety. A boundary fix constrains what your argument can do. It does not make the dependency trustworthy, and every answer carries a residual-risk block naming what it cannot cover.
- A missed relation is a false negative. Reachability comes from static per-package summaries. Treat the output as evidence for triage order, not proof of non-exploitability.
- Python and PyPI today. That is the declared scope of this feature.
- The signature authenticates a decision, not a deployment. It proves VDB decided this over the submitted abstraction; it does not prove the abstraction matches what you shipped.