How it works
Seven steps from a lockfile to a decision. Each one writes down what it found, so the decision at the end can be read backwards.
The pipeline
From the lockfile to the decision
- Inventory. Every component in
package-lock.json, with its development, direct and transitive flags. - Advisories. OSV for the advisories themselves, FIRST EPSS for the probability of exploitation in the next thirty days, and the CISA KEV catalogue for exploitation already seen in the wild. All of it cached in a local SQLite file.
- Code. A call graph of your source using the TypeScript compiler parser, Express routes as entry points with their mount prefixes and authentication guards, and a hint for where request data enters a chain.
- Reachability. One of six tiers per advisory, each carrying a confidence level.
- Exposure. Whether a Dockerfile exposes a port, whether compose publishes one, and whether the application binds only to loopback.
- Decision. An SSVC-style call, a score from 0 to 100 for ordering, and the reasons in plain English.
- Output. A table in the terminal, a full JSON report, and an OpenVEX document.
Tiers
What reachable means here
| Tier | Meaning | Default |
|---|---|---|
dev-only | A development dependency, so it is not in the shipped artifact. | Set aside |
unused | Installed, but no source file imports it. | Set aside |
imported-not-called | Imported, but none of the vulnerable functions are called. | Set aside |
imported-unknown-symbol | Imported, and NextFix has no function-level data for this advisory yet. | Watch |
called-unreached | The vulnerable function is called, but no HTTP entry point leads there. | Watch |
reachable | An entry point leads to the vulnerable call. | Fix next cycle |
Confidence
A tier is only as good as the data behind it, so every tier is shown with one of three confidence levels.
- Confirmed. The vulnerable functions for that advisory were curated by hand.
- Derived. The functions were extracted from the advisory fix commit and reviewed by a person. This is the next piece of work.
- Package-level. There is no function-level data, so the answer stops at whether the package is loaded.
Grading
What turns reachable into fix now
Reaching the vulnerable call is the start of the question. A finding is then weighed on who can get to the entry point and what the outside world is doing with the advisory.
- Authentication. An entry point with no guard counts for more than one behind a login.
- Request data. Whether user-controlled input travels down the chain into the vulnerable call.
- Exposure. Whether the service is published on a port rather than bound to loopback.
- Exploitation. The EPSS score and percentile, and whether the CVE is in the CISA KEV catalogue.
- Severity. The CVSS vector, which contributes rather than decides.
Output
The same answer in a terminal
The dashboards are the comfortable way in, and everything behind them is available from the command line for a pipeline to call.
NextFix 0.3.0 — express-shop 1.0.0 53 components, 20 advisories → 20 findings Act 1 · Attend 3 · Track* 2 · Track 14 80% of findings need no action right now, with evidence. Entry points: 4 (3 unauthenticated) · network: published on 3000 DECISION SCORE PACKAGE ADVISORY REACH ENTRY FIX Act 81 lodash@4.17.15 CVE-2021-23337 reachable POST /preview → 4.17.21 Attend 66 express@4.17.1 CVE-2024-29041 reachable GET /go → 4.19.2 Track* 42 lodash@4.17.15 CVE-2020-8203 called, unreached → 4.17.19 Track 10 moment@2.29.1 CVE-2022-24785 not imported → 2.29.2 Track 6 minimist@1.2.5 CVE-2021-44906 dev only → 1.2.6
Output from the sample application that ships with NextFix, shortened to fit. The JSON report and the OpenVEX document carry the same findings in full.
Limits
What it cannot tell you yet
Reachability analysis is a claim about your code, so it is worth being exact about where the claim stops.
- One ecosystem so far. Node.js projects with an npm lockfile. Python and Go are on the roadmap.
- Express entry points. Other frameworks are recognised as code but their routes are not yet read as entry points.
- Function data is thin. A curated seed covers a handful of advisories. Everything else is answered at package level, and NextFix says so rather than guessing.
- The taint hint is a heuristic. It follows request data one level deep along a chain, which is useful for ranking and is not a proof.
- Dynamic loading is invisible. A call reached only through a computed require or a runtime plugin will not appear in the graph.
A finding that NextFix sets aside is a judgement backed by stated evidence, and you can overrule it. The evidence is there so that you can check the reasoning rather than take the verdict on faith.