| name | cve-reachability |
| description | Assess if a CVE in a dep is reachable from project code using 15-signal union (runtime/static/external). Outputs audit trail. Security-grade, conservative. |
| allowed-tools | Read, Bash, Grep, Glob, WebFetch, Agent |
CVE Reachability — Security-Grade Assessment
Purpose
Given a CVE in a dependency, answer with evidence: "Is this vulnerable function reachable from my code?" Security bias: false-positive (saying reachable when not) is harmless; false-negative (saying not reachable when it is) is catastrophic (missed patch).
This is Scalpel's killer security feature. Uses multi-signal union — see runtime-static-hybrid and call-graph-soundness skills for the underlying framework.
Inputs
User provides one of:
- CVE identifier (e.g.,
CVE-2024-12345)
- Package + vulnerable function name (e.g.,
urllib3:SecurityWarning)
- Package + file + line range
Procedure (7 phases)
Phase 1 — Locate Vulnerable Function in Dep Source
- Find the installed package in the project's dep tree (
site-packages/, node_modules/, etc.)
- Identify the specific function/method the CVE references
- If CVE text only gives a file + line range: grep for function definitions in that range
Record: (dep_name, version, file_path, function_name, line_start, line_end)
Phase 2 — Build / Load Scalpel Genome
If not already built for this project:
scalpel trace --lang python -- pytest tests/
scalpel analyze .
Loads genome with nodes for all project + dep functions.
Phase 3 — Run Multi-Signal Query
For each of these 15 signals, determine if the vulnerable function shows evidence. Each signal is independent.
| # | Signal | Query |
|---|
| 1 | Static reachability (BFS from project entrypoints) | Does path exist in genome from main/tests/public API → vulnerable_fn? |
| 2 | Runtime observed (CI tests) | Is the function in .scalpel/traces/ci/*? |
| 3 | Runtime observed (staging) | .scalpel/traces/staging/* |
| 4 | Runtime observed (prod canary) | .scalpel/traces/prod-canary/* |
| 5 | APM spans (Datadog/NewRelic) | Query APM API for function in last 30d |
| 6 | Sentry stack traces | Search Sentry for function name in error stacks |
| 7 | Test coverage report | pytest-cov, jacoco — was function executed? |
| 8 | String-name literal scan | Grep project source for the function name as string literal |
| 9 | Bytecode LOAD_NAME opcodes | Walk project's .pyc for name reference |
| 10 | Public API re-export | Is function in __all__ of any project module? |
| 11 | Framework-dispatched | Is function decorated by a known framework? |
| 12 | Doc mention | Grep README/docs/comments for function name |
| 13 | Type annotation refers | Any project file declares x: VulnerableClass? |
| 14 | Configuration reference | YAML/JSON configs reference it by path? |
| 15 | Call-site count (project calls into dep) | How many project files import from the vulnerable dep module? |
Phase 4 — Confidence Scoring
Apply weights from runtime-static-hybrid skill (Evidence Weight Table):
- Runtime in prod: 10, Runtime in staging: 8, Runtime in CI: 7
- APM: 8, Sentry stacks: 8
- Static reachable: 5
- Bytecode reference: 6, Test coverage: 7
- String scan: 2, Public API: 4, Framework deco: 6, Doc mention: 2
Sum of weights = reachability confidence.
Phase 5 — Classification
| Total weight | Verdict | Action |
|---|
| ≥ 10 (any runtime) | DEFINITELY REACHABLE | Patch immediately. Document exploit path. |
| 5-9 | LIKELY REACHABLE | Patch. Document evidence. Suggest runtime-trace to confirm. |
| 1-4 | POSSIBLY REACHABLE | Patch unless justified by threat model. |
| 0 | NOT EVIDENTLY REACHABLE | Document which 15 signals were checked. Note concessions (reflection and dynamic dispatch can't be proven absent). |
Never report "not reachable" unconditionally — always say "not evidently reachable across N signals checked". This matters for security audits.
Phase 6 — Audit Trail Output
Produce a structured report:
CVE-2024-XXXXX — <package>:<function>
Verdict: LIKELY REACHABLE (confidence 12)
Evidence:
[+] Static BFS: path main→handler→dep.func (weight 5)
[+] Sentry stack trace 2026-03-12 includes func (weight 8)
[-] Runtime CI: not observed
[-] Runtime prod: not observed
[-] Bytecode LOAD_NAME: not found
[-] String scan: no literal match in project source
... (all 15 signals listed)
Recommendation: Patch dep to version >= X.Y.Z.
Suggested investigation: run scalpel trace against realistic prod scenario to confirm runtime reachability.
Exploit path: <file:line> invokes <intermediate_fn> which calls <dep.vulnerable_fn>.
Phase 7 — Output + Action
Write report to .scalpel/security/CVE-YYYY-XXXXX-assessment.md.
Commit the report to the repo for future reference (fulfills security audit evidence requirement).
Suggest next actions:
- Patch command (if fix version known):
pip install <pkg>==<fixed>
- Waiver justification if not patching (rare — only when threat model excludes)
- Test: after patch, re-run scalpel prune safety to confirm no regression
Concessions (Soundiness)
Cannot prove absence of reachability through:
- Reflection / string-based dispatch
- Dynamic imports via
importlib-style APIs
- Deserialization-triggered code paths
- Cross-process dispatch (subprocess, RPC)
- Proxy / attribute-interception protocols
If CVE is triggered through any of the above patterns, mark report: "CONCESSION: static + runtime analysis cannot prove absence if dispatch is via [pattern]. Treat as reachable under security assumptions."
Integration with External Tools
Snyk / Dependabot integration
If user has Snyk/Dependabot CVE alerts, ingest them:
snyk test --json > /tmp/snyk.json
scalpel cve scan --from /tmp/snyk.json
Run Phase 3-7 for each CVE.
OSV database
For offline CVE lookups, download OSV.dev snapshot:
curl -sL https://osv-vulnerabilities.storage.googleapis.com/PyPI/all.zip -o pypi-osv.zip
scalpel cve scan --osv pypi-osv.zip --package <pkg>@<ver>
Validation Checklist
Before closing a CVE assessment:
Anti-Patterns
- Reporting "not reachable" from static-only analysis (static is incomplete)
- Missing the concessions section (CVEs via reflection become invisible)
- Skipping saturation gate (< 30 days coverage = don't trust "not runtime-observed" signal)
- Producing verdict without audit trail (can't pass security audit)
- Equating "not in call graph" with "not reachable" (graph has gaps)
Performance Budget
- Single CVE assessment: < 30 seconds end-to-end
- 100 CVEs batch scan: < 5 minutes
- Memory: < 500 MB for a medium project + all signal sources