Overview
Add a new sibling crate dokono-cve. While dokono-rs solves "changed lines → affected bins", dokono-cve solves "vulnerable function in a dependency crate → bins that reach it". It reuses dokono-core's upward BFS as-is.
In one sentence
When a vulnerability is found in one of your repository's dependencies, determine whether your bins actually have a call path that can reach the vulnerable function, and if so, display the call chain from main down to that function.
Triggers (when to use)
Manual invocation by an auditor:
cargo audit reported a CVE in a dependency crate
- A GitHub Security Advisory notified you that "a crate you use has a vulnerability"
- Someone internally said "isn't this function dangerous?" (a 0-day not yet in any DB)
→ When you want to decide: "OK, does our code actually hit it or not?"
Inputs
Three patterns:
- Output of
cargo audit --json (evaluate all advisories at once)
- A single advisory ID (e.g.
RUSTSEC-2023-XXXX)
- A vulnerable symbol given directly as a string (e.g.
openssl::ssl::SslContext::new, for 0-days)
Plus, always required: the workspace path.
Output sketch
```
CVE-2023-XXXX openssl::ssl::SslContext::new
✗ services/src/bin/api.rs [REACHABLE]
main
└─ build_router services/src/http/mod.rs:45
└─ TlsConfig::load services/src/tls/mod.rs:18
└─ SslContext::new (vulnerable)
✓ services/src/bin/migrate.rs [NOT REACHABLE]
```
Differentiation from existing tools
What cargo audit answers is only "does the dependency tree contain a vulnerable crate?" (crate granularity). In reality:
- You might not be calling the vulnerable function at all
- Even if you are, the
migrate bin might not reach it
- It might only be called from under
cfg(test) and never ship to production
cargo audit cannot distinguish any of these. dokono-cve looks at real reachability at symbol × bin granularity, enabling differentiated decisions like "patch api.rs immediately, leave the others on watch".
Out of scope (boundaries)
- Applying patches (
cargo update is a separate tool)
- Computing CVSS scores (we just display what's in the advisory)
- Calls through an intermediate crate (A → third-party crate → vulnerable crate) are not traced (known limitation)
- Dynamic behavior (reflection etc.) is not handled (static analysis only)
Design direction (conclusions from up-front investigation)
1. How to find the first call site on the workspace side
Adopted: synthesized use-site + goto-def → run textDocument/references at the resulting dep-side location.
- rust-analyzer's
Definition::search_scope walks reverse_dependencies for pub items, so invoking references at the declaration site in the dependency crate returns hits on the workspace side (crates/ide-db/src/search.rs)
- However, there is no LSP-level way to go from the string
openssl::ssl::SslContext::new to the declaration site in the dep crate (workspace/symbol is fuzzy and the ranking is unstable rust-analyzer#16491)
- → Write
use openssl::ssl::SslContext; into a synthesized file and goto-def from there. This lets rust-analyzer's name resolution handle re-exports / aliases for us
- Resolving manually via AST matching would mean re-implementing
use as and type-alias chains, so we don't go that route
2. Extracting vulnerable symbols from cargo audit JSON / advisories
Adopted: depend on the rustsec crate and read affected.functions. MVP limited to Tier A.
cargo audit --json is Serialize-wired straight from rustsec::Report. vulnerabilities.list[].advisory.affected.functions is included as-is (rustsec/cargo-audit#41)
- Format:
BTreeMap<FunctionPath, Vec<VersionReq>>, where FunctionPath is a normalized Rust path
- Coverage is realistically a minority of advisories (openssl is rich, tokio/hyper are nearly empty)
- Tier A (
affected.functions present): convert directly into seeds
- Tier B (absent): return
reachable: unknown (no affected.functions in DB) and stop. We explicitly degrade to the same granularity as cargo audit
- Extending Tier B to "seed every usage site of the crate in question" is not in the first version (it would kill the value prop)
3. Handling of #[cfg(test)]
Adopted: during BFS, inspect each reference's enclosing function and its parent module chain for #[cfg(test)], surface test paths under a separate label, and exclude them from the headline result.
- rust-analyzer defaults to
cargo.allTargets=true and fully indexes #[cfg(test)] mod tests
textDocument/references responses do not distinguish prod vs test
- The
references.excludeTests setting only targets #[test] function bodies; helpers under #[cfg(test)] mod tests slip through (rust-analyzer#18573)
- → We have to classify on the client side. Output is one of three labels:
[REACHABLE] / [REACHABLE FROM TESTS ONLY] / [NOT REACHABLE]
4. Exit codes for CI
0: analysis succeeded, zero reachable vulnerabilities
1: analysis succeeded, vulnerabilities reachable from a production path exist
2: tool error
- Test-only reachability is exit 0 by default;
--strict promotes it to 1
5. Crate name
dokono-cve (to keep parity with dokono-rs / dokono-core).
Impact on dokono-core
No changes are required to dokono-core as it stands today. dokono-cve only needs to implement the bfs::LspBackend trait to reuse everything:
- Seed construction is fully decoupled from
symbols::pick_at_lines
bfs::run is backend-agnostic
entrypoints::load_bin_entrypoints is reused
lsp::client::Client / lsp::backend::Backend are reused
The #[cfg(test)] detection logic is implemented locally inside dokono-cve (so the behavior of dokono-rs proper is unchanged).
Known limitations (inherited from dokono-rs)
- Missed references in spots where macro expansion is incomplete
- Missed references in code that depends on generics / type inference
- We do not expand downward through trait impls
- Vulnerable calls through an intermediate crate are not traced (specific to dokono-cve)
References
Overview
Add a new sibling crate
dokono-cve. Whiledokono-rssolves "changed lines → affected bins",dokono-cvesolves "vulnerable function in a dependency crate → bins that reach it". It reusesdokono-core's upward BFS as-is.In one sentence
Triggers (when to use)
Manual invocation by an auditor:
cargo auditreported a CVE in a dependency crate→ When you want to decide: "OK, does our code actually hit it or not?"
Inputs
Three patterns:
cargo audit --json(evaluate all advisories at once)RUSTSEC-2023-XXXX)openssl::ssl::SslContext::new, for 0-days)Plus, always required: the workspace path.
Output sketch
```
CVE-2023-XXXX openssl::ssl::SslContext::new
✗ services/src/bin/api.rs [REACHABLE]
main
└─ build_router services/src/http/mod.rs:45
└─ TlsConfig::load services/src/tls/mod.rs:18
└─ SslContext::new (vulnerable)
✓ services/src/bin/migrate.rs [NOT REACHABLE]
```
Differentiation from existing tools
What
cargo auditanswers is only "does the dependency tree contain a vulnerable crate?" (crate granularity). In reality:migratebin might not reach itcfg(test)and never ship to productioncargo auditcannot distinguish any of these.dokono-cvelooks at real reachability at symbol × bin granularity, enabling differentiated decisions like "patchapi.rsimmediately, leave the others on watch".Out of scope (boundaries)
cargo updateis a separate tool)Design direction (conclusions from up-front investigation)
1. How to find the first call site on the workspace side
Adopted: synthesized use-site + goto-def → run
textDocument/referencesat the resulting dep-side location.Definition::search_scopewalksreverse_dependenciesforpubitems, so invoking references at the declaration site in the dependency crate returns hits on the workspace side (crates/ide-db/src/search.rs)openssl::ssl::SslContext::newto the declaration site in the dep crate (workspace/symbolis fuzzy and the ranking is unstable rust-analyzer#16491)use openssl::ssl::SslContext;into a synthesized file and goto-def from there. This lets rust-analyzer's name resolution handle re-exports / aliases for ususe asand type-alias chains, so we don't go that route2. Extracting vulnerable symbols from
cargo auditJSON / advisoriesAdopted: depend on the
rustseccrate and readaffected.functions. MVP limited to Tier A.cargo audit --jsonis Serialize-wired straight fromrustsec::Report.vulnerabilities.list[].advisory.affected.functionsis included as-is (rustsec/cargo-audit#41)BTreeMap<FunctionPath, Vec<VersionReq>>, whereFunctionPathis a normalized Rust pathaffected.functionspresent): convert directly into seedsreachable: unknown (no affected.functions in DB)and stop. We explicitly degrade to the same granularity ascargo audit3. Handling of
#[cfg(test)]Adopted: during BFS, inspect each reference's enclosing function and its parent module chain for
#[cfg(test)], surface test paths under a separate label, and exclude them from the headline result.cargo.allTargets=trueand fully indexes#[cfg(test)] mod teststextDocument/referencesresponses do not distinguish prod vs testreferences.excludeTestssetting only targets#[test]function bodies; helpers under#[cfg(test)] mod testsslip through (rust-analyzer#18573)[REACHABLE]/[REACHABLE FROM TESTS ONLY]/[NOT REACHABLE]4. Exit codes for CI
0: analysis succeeded, zero reachable vulnerabilities1: analysis succeeded, vulnerabilities reachable from a production path exist2: tool error--strictpromotes it to 15. Crate name
dokono-cve(to keep parity withdokono-rs/dokono-core).Impact on dokono-core
No changes are required to
dokono-coreas it stands today.dokono-cveonly needs to implement thebfs::LspBackendtrait to reuse everything:symbols::pick_at_linesbfs::runis backend-agnosticentrypoints::load_bin_entrypointsis reusedlsp::client::Client/lsp::backend::Backendare reusedThe
#[cfg(test)]detection logic is implemented locally inside dokono-cve (so the behavior of dokono-rs proper is unchanged).Known limitations (inherited from dokono-rs)
References
#[cfg(test)]