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 for sink-first taint: Whole-reporgctl taint --sinkrequiresdiscover --with-cfg. Manifest/deps checkand caller searches work on a structural index alone. Single-functionrgctl slice --taintcan build CFG/PDG lazily for one criterion, but OpenVEX reachability audits that need sink-first taint should still pre-build the archive with--with-cfg.
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:
# In-tree fixture used below:
# tests/fixtures/osv/jackson-CVE-2022-42004.json
rgctl -f json vuln triage --osv tests/fixtures/osv/jackson-CVE-2022-42004.json
JSON Output:
{
"aliases": ["CVE-2022-42004", "GHSA-jfh8-c2jp-hdp4"],
"command": "vuln triage",
"id": "GHSA-jfh8-c2jp-hdp4-bench",
"packages": [
{
"ecosystem": "Maven",
"name": "com.fasterxml.jackson.core:jackson-databind",
"introduced": "2.0.0",
"fixed": "2.14.0",
"purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind",
"affected_methods": [
"com.fasterxml.jackson.databind.ObjectMapper.readValue",
"com.fasterxml.jackson.databind.ObjectMapper.readTree",
"com.fasterxml.jackson.databind.ObjectMapper.convertValue",
"com.fasterxml.jackson.databind.ObjectReader.readValue"
],
"affected_classes": [
"com.fasterxml.jackson.databind.ObjectMapper",
"com.fasterxml.jackson.databind.ObjectReader"
]
}
],
"schema_version": 1,
"summary": "In FasterXML jackson-databind before 2.14.0, use of deeply nested arrays can cause a denial-of-service (stack overflow) via a large depth of nesting.",
"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 example/coolstore -f json deps check \
--osv tests/fixtures/osv/jackson-CVE-2022-42004.json
Output when not affected (Fast Exit) — CoolStore manifests do not pull jackson-databind in range:
{
"command": "deps check",
"osv_id": "GHSA-jfh8-c2jp-hdp4-bench",
"verdict": "not_affected",
"checked": ["manifests"],
"matched": [],
"schema_version": 1
}
Output when candidate is affected (shape when a manifest version is in range):
{
"command": "deps check",
"osv_id": "GHSA-jfh8-c2jp-hdp4-bench",
"verdict": "affected_candidate",
"checked": ["manifests"],
"matched": [
{
"ecosystem": "Maven",
"name": "com.fasterxml.jackson.core:jackson-databind",
"version": "2.9.8",
"in_range": true,
"provenance": "pom.xml"
}
],
"schema_version": 1
}
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 example/coolstore -f json vuln analyze \
--osv tests/fixtures/osv/jackson-CVE-2022-42004.json
JSON Output (CoolStore early-exit path — dependency absent):
{
"schema_version": "1",
"osv_id": "GHSA-jfh8-c2jp-hdp4-bench",
"deps_verdict": "not_affected",
"exploitability": "not_affected",
"phases": ["triage", "deps_check"],
"notes": "Early exit after deps check",
"openvex": {
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "vex:GHSA-jfh8-c2jp-hdp4-bench",
"author": "rgctl",
"role": "document creator",
"tooling": "rgctl vuln analyze",
"version": "1",
"statements": [
{
"vulnerability": "GHSA-jfh8-c2jp-hdp4-bench",
"products": ["Maven:com.fasterxml.jackson.core:jackson-databind"],
"status": "not_affected",
"justification": "component_not_present",
"impact_statement": "Dependency not present or version not in vulnerable range."
}
]
}
}
When the package is present but unused, later phases (package_resolve, caller_scan, sink_taint, openvex) run and exploitability may become not_exploitable with justification vulnerable_code_not_in_execute_path.
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}'