Vulnerability and OSV Supply-Chain Analysis
Introduction
Traditional Software Composition Analysis (SCA) tools generate overwhelming false-positive fatigue: they scan dependency manifests (pom.xml, package.json, Cargo.toml) and flag every CVE matching a package version—regardless of whether the vulnerable function is ever imported, called, or exposed to external input.
rgctl provides an end-to-end supply-chain reachability and exploitability pipeline that bridges dependency triage with deep graph analysis:
OSV Advisory JSON
│
▼
[P0] vuln triage Normalize advisory & extract affected version ranges and methods
│
▼
[P1] deps check Match package manifests & optional bundled JARs / node_modules
│ ↳ Fast Exit: not_affected if dependency absent or not in range
▼
[P2/P3] find & callers Query import statements & verify if affected methods are ever called
│
▼
[P4] blast-radius Classify call paths against external attack boundaries (REST, CLI, web)
│
▼
[P5] taint Trace data flow from untrusted sources to the vulnerable sink (CFG/PDG)
│
▼
[P6] vuln analyze Synthesize exploitability verdict & generate machine-readable OpenVEX
All security commands are available as top-level verbs (vuln, deps, taint) and grouped under the security namespace (security vuln, security deps, security taint).
Use Cases
- Eliminating Vulnerability False Positives: Prove with code graph evidence that a vulnerable dependency method is never in the execution path, allowing safe deferral without patching.
- Automated OpenVEX Generation: Produce standardized, machine-readable OpenVEX documents justifying why specific CVEs are
not_affectedornot_exploitablefor compliance audits and SBOM pipelines. - Incident Response Triage: Rapidly determine which microservices actually call a newly disclosed 0-day vulnerability (such as a Jackson or Log4Shell deserialization sink).
- Sink-First Taint Verification: Confirm whether untrusted external HTTP request parameters can flow through the call graph into a security-sensitive sink.
Exploitability Verdicts
The orchestrator (vuln analyze) evaluates multi-phase evidence to produce one of four definitive verdicts:
| Exploitability Verdict | OpenVEX Status | Condition |
|---|---|---|
not_affected | not_affected | The dependency is not present in the project manifests/bundles, or its installed version is outside the advisory's vulnerable range. |
not_exploitable | not_affected (vulnerable_code_not_in_execute_path) | The dependency is installed in a vulnerable version range, but the code graph proves the affected methods/classes are never imported or called. |
exploitable | affected | The vulnerable method is called, reaches an external boundary, and forward data-flow taint confirms a reachable path from untrusted input. |
under_investigation | under_investigation | The dependency is present and called, but evidence is inconclusive (e.g. CFG was not built, or the primary sink cannot be definitively resolved). |
Indexing Prerequisites
The depth of analysis determines the required indexing flags:
# 1. Fast path (deps-only triage & manifest matching):
rgctl discover .
# 2. Deep reachability, sink-first taint, and firm OpenVEX justifications:
rgctl discover . --with-cfg --with-taint
[!IMPORTANT] Why
--with-cfgmatters: Without control-flow graphs (--with-cfg),deps checkand caller searches still work, buttaintcannot execute and OpenVEX cannot definitively assert that vulnerable code is not in the execution path. Always index with--with-cfgfor reachability audits.
Step-by-Step Walkthrough
Step 0: Normalize the Advisory (vuln triage)
Parse an OSV advisory JSON file (e.g., from GHSA, CVE, or PyPA) to extract affected packages, ecosystems, version boundaries, and affected function signatures:
rgctl -f json vuln triage --osv ./advisory.json
JSON Output:
{
"id": "GHSA-j276-x3v5-9988",
"summary": "Jackson-databind vulnerable to unsafe deserialization",
"packages": [
{
"ecosystem": "Maven",
"name": "com.fasterxml.jackson.core:jackson-databind",
"introduced": "2.0.0",
"fixed": "2.9.10.8"
}
],
"affected_methods": [
"com.fasterxml.jackson.databind.ObjectMapper.readValue"
],
"versions_array_not_authoritative": false
}
Step 1: Check Dependency Manifests (deps check)
Match the advisory against your project manifests (pom.xml, package.json, Cargo.toml, go.mod, etc.) and optional binary bundles (--include-jars, --include-node-modules):
rgctl -r "$REPO" -f json deps check --osv ./advisory.json --include-jars lib
Output when not affected (Fast Exit):
{
"osv_id": "GHSA-j276-x3v5-9988",
"verdict": "not_affected",
"checked": ["pom.xml", "lib/custom-lib.jar"],
"matched": []
}
Output when candidate is affected:
{
"osv_id": "GHSA-j276-x3v5-9988",
"verdict": "affected_candidate",
"checked": ["pom.xml"],
"matched": [
{
"ecosystem": "Maven",
"name": "com.fasterxml.jackson.core:jackson-databind",
"version": "2.9.8",
"in_range": true,
"provenance": "pom.xml"
}
]
}
If deps check returns not_affected, vuln analyze terminates early, immediately generating an OpenVEX document with not_affected (component_not_present).
Step 2: Check Import Presence and Method Callers
If the dependency is installed, query the graph to determine if your code actually imports or calls the affected symbols:
# Check if package imports exist in the graph
rgctl -r "$REPO" -f json find --package 'com.fasterxml.jackson.core:jackson-databind' -t import
# Find callers of the affected method
rgctl -r "$REPO" -f json callers ObjectMapper --methods readValue
rgctl handles package namespace resolution across ecosystems, including the Xalan dual path (resolving xalan:* to both Apache packages and JDK internal aliases like com.sun.org.apache.xalan.internal).
Step 3: Classify Attack Boundaries (blast-radius --classify-boundary)
If the method is called, determine whether callers are exposed to external entrypoints:
rgctl -r "$REPO" -f json blast-radius ObjectMapper.readValue --classify-boundary
Boundary catalogs automatically detect:
- Java / Jakarta: Spring
@RestController,@PostMapping, JAX-RS@Path, ServletdoGet/doPost. - Python: Flask
@app.route, Django views, FastAPI endpoints.
Step 4: Sink-First Taint Propagation (taint)
Trace data flow from untrusted external sources directly into the vulnerable sink method using the CFG/PDG graph:
rgctl -r "$REPO" -f json taint \
--sink ObjectMapper.readValue \
--source external \
--depth 8
JSON Output:
{
"sink": "ObjectMapper.readValue",
"sink_resolution": "resolved",
"sink_callers": ["OrderController.submitOrder"],
"paths": [
{
"source": "HttpServletRequest.getInputStream",
"sink": "ObjectMapper.readValue",
"hops": [
"OrderController.submitOrder",
"OrderService.parsePayload",
"ObjectMapper.readValue"
]
}
],
"honesty": "PDG confirmed forward taint path from external boundary"
}
If no path exists, taint confirms that untrusted inputs cannot flow into the sink.
Step 5: Full Orchestrated Analysis & OpenVEX (vuln analyze)
Run the complete orchestrated pipeline with a single command:
rgctl -r "$REPO" -f json vuln analyze --osv ./advisory.json --include-jars lib
JSON Output:
{
"schema_version": "1",
"osv_id": "GHSA-j276-x3v5-9988",
"deps_verdict": "affected_candidate",
"exploitability": "not_exploitable",
"phases": [
"triage",
"deps_check",
"package_resolve",
"caller_scan",
"sink_taint",
"openvex"
],
"notes": "Vulnerable package is present in pom.xml, but affected method ObjectMapper.readValue has 0 call sites in the repository.",
"openvex": {
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "urn:openvex:rgctl:analysis:GHSA-j276-x3v5-9988",
"author": "rgctl-security",
"timestamp": "2026-10-10T03:30:00Z",
"statements": [
{
"vulnerability": {
"name": "GHSA-j276-x3v5-9988"
},
"products": [
{
"@id": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.8"
}
],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"impact_statement": "Affected method has zero callers in application call graph"
}
]
}
}
The security Namespace
All vulnerability and dependency commands are available under the security parent command:
| Top-Level Command | Canonical security Equivalent | Description |
|---|---|---|
rgctl vuln triage | rgctl security vuln triage | Normalize OSV advisory |
rgctl deps check | rgctl security deps check | Match manifests and bundles |
rgctl vuln analyze | rgctl security vuln analyze | Orchestrate analysis & OpenVEX |
rgctl taint | rgctl security taint | Sink-first external taint flow |
Declarative Taint Rule Overlays
rgctl uses a declarative YAML taint engine (crates/rgctl-analysis/rules/taint/). You can supply project-specific rules or custom vulnerability sink overlays:
- Place rule files in
.rgctl/taint-rules.d/my-rules.yaml. - Or pass rules directly via the CLI:
rgctl -f json taint \
--sink MyCustomValidator.decode \
--source external \
--taint-rules ./custom-rules.yaml
Example Taint Rule Format:
id: CWE-502-deserialization
name: Unsafe Object Deserialization
severity: high
cwe: 502
sources:
- kind: external
pattern: "HttpServletRequest.*"
sinks:
- pattern: "ObjectMapper.readValue"
arg_indices: [0, 1]
sanitizers:
- pattern: "SecurityValidator.sanitize"
CLI Flag Reference
vuln triage Options
| Flag | Default | Description |
|---|---|---|
--osv <PATH> | Required | Path to OSV advisory JSON file. |
deps check Options
| Flag | Default | Description |
|---|---|---|
--osv <PATH> | Required | Path to OSV advisory JSON file. |
--include-jars <DIR> | [] | Directories containing bundled .jar archives to inspect. |
--include-node-modules <DIR> | [] | Directories containing bundled node_modules/ to inspect. |
vuln analyze Options
| Flag | Default | Description |
|---|---|---|
--osv <PATH> | Required | Path to OSV advisory JSON file. |
--include-jars <DIR> | [] | Directories containing bundled .jar archives. |
--include-node-modules <DIR> | [] | Directories containing bundled node_modules/. |
taint Options
| Flag | Default | Description |
|---|---|---|
--sink <SYMBOL> | Required | Target sink function or method (e.g. ObjectMapper.readValue). |
--source <KIND> | external | Taint source kind (only external supported in v1). |
--depth <N> | 8 | Maximum caller hops to traverse upstream. |
--taint-rules <PATH> | — | Path to custom YAML taint ruleset file. |
Working with jq
# 1. Extract exploitability verdict and notes
rgctl -f json vuln analyze --osv ./advisory.json \
| jq '{osv_id, exploitability, notes}'
# 2. Extract OpenVEX statements directly
rgctl -f json vuln analyze --osv ./advisory.json \
| jq '.openvex.statements[] | {status, justification, impact: .impact_statement}'
# 3. Check matched dependencies
rgctl -f json deps check --osv ./advisory.json \
| jq '.matched[] | {name, version, in_range, provenance}'