How the remediation cost is calculated
The method, the constants and the assumptions behind every dollar figure on these dashboards — and the things the figures do not include.
What the number means
Each priced vulnerability carries an estimate of the engineering time needed to write and validate its fix, based on the fix the project actually published. The estimate comes from a rubric with three dimensions, each scored 1 to 5. Their sum maps to engineer-days on a fixed curve, and days convert to dollars at a fixed daily rate. The same rubric is applied to every project.
The formula
D1 is measured from the fix itself. D2 and D3 come from what kind of vulnerability it is. Severity is not an input — see Severity and CVSS.
D1 — Implementation effort
How big and how spread out the change is: the lines added plus lines deleted, and the number of files, counting only code an engineer wrote (tests, documentation and generated files are excluded — see What counts as written code). The lines set the score: the first row whose line limit the fix fits within. The file count can raise it by one row at most, when the files exceed that row's file limit. When 90% or more of the lines are deletions — a fix that removes a feature — the lines column reads the added lines only; the reasoning and validation scores stay.
| D1 | Lines | Files |
|---|---|---|
| 1 | ≤ 10 lines | ≤ 1 file |
| 2 | ≤ 50 lines | ≤ 2 files |
| 3 | ≤ 200 lines | ≤ 5 files |
| 4 | ≤ 500 lines | ≤ 10 files |
| 5 | more than 500 lines | more than 10 files |
So a 74-line change spread over 15 files is D1 4 (lines row 3, raised one row by its files), not D1 5, and one line changed in three files is D1 2. A fix that only upgrades a dependency is set to D1 1 (see below). These rules are grading G7, used since 2026-10-11; before it the larger of the two rows set D1.
D2 & D3 — Reasoning and validation difficulty
D2 — reasoning: how hard it is to find the root cause and decide on the right fix. D3 — validation: how hard it is to confirm the fix works and breaks nothing else. Both are set by the vulnerability class. A use-after-free takes more reasoning than an open redirect, and a request-smuggling fix is harder to test than it is to reason about.
| Level | D2 | D3 | Vulnerability classes | Priced records |
|---|---|---|---|---|
| Specialist | 5 | 5 | JIT compiler bugs (incl. miscompilation) | 33 |
| Expert | 4 | 4 | Sandbox escape, Race condition, Unsafe deserialization, Type confusion | 369 |
| Expert | 4 | 3 | Use-after-free, Memory corruption, Buffer overflow | 548 |
| Skilled | 3 | 4 | HTTP request smuggling | 100 |
| Skilled | 3 | 3 | Mitigation bypass, Privilege escalation, Undefined behavior, Invalid pointer, Authentication / authorization bypass, Improper access control, Same-origin policy bypass, Server-side request forgery (SSRF), Command injection, SQL injection, XML injection | 5,429 |
| Standard | 2 | 2 | Missing boundary check, Integer overflow, Uninitialized memory, Information disclosure, Path traversal, Cross-site scripting (XSS), Cross-site request forgery (CSRF), Open redirect | 3,147 |
| Routine | 1 | 1 | Denial of service, Spoofing | 1,477 |
| Default | 2 | 2 | Any class not listed above, including other | 549 |
When a class name contains more than one listed term, the first term in the rubric's own list wins, and JIT comes first — so a record classed “JIT use-after-free” scores as JIT. 15 projects (Apache Cassandra, Apache Tomcat, Dify, Flowise, Frappe, GitLab, Grafana, Keycloak, Mattermost, Moodle, Nextcloud, OpenMRS, PostgreSQL, Redis, Strapi) define a few component adjustments of ±1 — for example, Tomcat's HTTP/2 and TLS code is one step harder on both dimensions, its Manager app one step easier. Scores always stay within 1–5. 328 of 11,652 priced records (3%) carry a D2 or D3 different from their class default, through these adjustments or project-level calibration.
Combined score → engineer-days → dollars
The combined score (3–15) maps to engineer-days on a curve that gets steeper toward the top: moving from 8 to 9 adds 2 days, moving from 14 to 15 adds 15. Hard fixes are disproportionately expensive, and the curve says so. The four difficulty buckets used in the charts are ranges of the combined score.
| Combined | Engineer-days | Hours | Cost | Bucket | Priced records |
|---|---|---|---|---|---|
| 3 | 0.5 | 4 | $625 | Low | 302 |
| 4 | 0.7 | 6 | $875 | Low | 484 |
| 5 | 1 | 8 | $1,250 | Low | 1,410 |
| 6 | 1.5 | 12 | $1,875 | Low | 1,383 |
| 7 | 2 | 16 | $2,500 | Low | 2,156 |
| 8 | 3 | 24 | $3,750 | Medium | 2,206 |
| 9 | 5 | 40 | $6,250 | Medium | 2,029 |
| 10 | 7 | 56 | $8,750 | High | 904 |
| 11 | 10 | 80 | $12,500 | High | 634 |
| 12 | 15 | 120 | $18,750 | Extra High | 91 |
| 13 | 20 | 160 | $25,000 | Extra High | 49 |
| 14 | 30 | 240 | $37,500 | Extra High | 2 |
| 15 | 45 | 360 | $56,250 | Extra High | 2 |
These are the published tables. The Effort Model page lets you change the D1 bands, the class table behind D2 and D3, the component adjustments and this curve, and see every figure recalculated. Those tables are kept in your browser only; this page and the published records always show the tables above.
Constants
| Constant | Value | Note |
|---|---|---|
| Annual cost of one engineer | $250,000 | Fully loaded. Editable in the Overview's cost assumptions; dollars rescale, days do not. |
| Working days per year | 200 | |
| Hours per working day | 8 | Used only to show hours. |
| Cost per engineer-day | $1,250 | $250,000 ÷ 200 |
| Cost per engineer-hour | $156.25 | $250,000 ÷ 1,600 hours |
| Record cost | days × $1,250 | Rounded to the nearest dollar. |
One rate is used for every project and region. No productivity adjustment for AI-assisted coding is applied: nothing in the study measures one, and a modelled discount on a modelled cost would read as a finding.
Severity and CVSS
Severity is the rating the project or its advisory publishes, normalised to four levels: critical, high, moderate, low. The CVSS base score is recorded when the advisory provides one. Neither is used to compute cost — they are shown so cost can be compared against them.
- 10,217 of 11,652 priced records carry a CVSS score; 1,435 do not. 939 of those are Firefox, whose advisories rate impact on Mozilla's own four-level scale and carry no CVSS score.
- On 160 records the published label differs from the CVSS band (critical 9.0–10.0, high 7.0–8.9, moderate 4.0–6.9, low 0.1–3.9). We keep the project's label.
- 96 priced records have no published severity: on 73 it was
assigned by our own grader from the vulnerability class, and on 23 the label is a placeholder ("moderate") because nothing published rates them.
On 2026-10-11 the 48 corpus records whose "moderate" was a generator placeholder took the CVSS rating NVD publishes
(one unpriced Keycloak record has none). The registry's
severity_sourcecolumn marks them (defaulted,inferred); the others readcvssorpublished.
How cost relates to severity
Cost has two parts: D1, measured from the fix, and D2+D3, set by the vulnerability class. Split that way, severity says almost nothing about how big a fix is. The weak link between severity and cost comes from the class table.
| Published severity | Priced records | Median engineer-days | Median written lines | Median D2+D3 |
|---|---|---|---|---|
| Low | 822 | 2 | 25 | 4 |
| Moderate | 5,429 | 2 | 24 | 4 |
| High | 4,300 | 2.3333 | 31 | 6 |
| Critical | 1,101 | 3 | 36 | 6 |
Rank correlation with severity (0 = none, 1 = perfect): engineer-days +0.12; written lines +0.08; D2+D3 +0.15. Memory-corruption, JIT and injection classes carry both higher severity ratings and higher reasoning and validation scores, so part of any severity–cost relationship is built into the rubric rather than measured from the fixes.
What counts as written code
D1 measures what an engineer wrote, so the raw diff is filtered first. Across priced records the median fix is 67 changed lines in the raw diff and 28 lines of written code.
- Excluded: tests; documentation, changelogs and release notes; files produced by tools
(lockfiles such as
package-lock.jsonoruv.lock, minified bundles, generated parsers, version headers); and third-party code copied into the repository. - Dependency upgrades: when the fix is pulling in a patched third-party version — a wholesale update of a bundled library (more than 3 files or 200 lines), or a change recorded only in lockfiles — D1 is set to 1. The engineer chose and validated someone else's fix; the library's internals were not their work. 18 priced records are dependency upgrades. A small hand patch to bundled code does count.
- Backports: when the same fix lands on several release branches, it is counted once (the copy on the main branch). Several commits that fix the vulnerability one after another on the same branch all count.
- Since 2026-10-11: non-English translation files, requirements files for tests and
static-analysis baselines do not count; a Python dependency manifest (
requirements.txt) does; binary files are not counted as files. - Moved code: lines relocated rather than written (for example, a refactor that moves a function into a shared helper in the same commit) are discounted. Detection uses git's moved-code comparison, which needs contiguous blocks, so coincidental matches of single lines such as a closing brace are not treated as moved. 66 priced records were adjusted.
- Commits that only mention a vulnerability — a later follow-up, a test added months afterwards, a commit citing an old vulnerability as background — are not treated as its fix.
Worked example
Node.js CVE-2025-55130 —
authentication / authorization bypass. Fix commit
3296b2e734 “lib,permission: require full read and write to symlink APIs”.
| File | Kind | Lines | Counted for D1? |
|---|---|---|---|
| lib/fs.js | code | 32 | counted |
| lib/internal/fs/promises.js | code | 20 | counted |
| test/fixtures/permission/fs-symlink-target-write.js | test | 18 | excluded |
| test/fixtures/permission/fs-symlink.js | test | 18 | excluded |
| test/parallel/test-permission-fs-symlink-relative.js | test | 10 | excluded |
| test/parallel/test-permission-fs-symlink.js | test | 14 | excluded |
- The raw diff is 112 lines in 6 files. After exclusions the engineer wrote 52 lines in 2 files.
- 52 lines is lines band 3, and 2 files do not raise it — so D1 = 3.
- The class sets D2 = 3 and D3 = 3.
- Combined 3 + 3 + 3 = 9 → 5 engineer-days → 5 × $1,250 = $6,250 (Medium bucket).
It is also a boundary case: 52 written lines is 2 over the 50-line limit of the band below. At 50 lines or fewer it would score D1 2, combined 8, 3 days ($3,750).
What is not priced, and why
3,584 of 15,236 records (24%) carry no cost. They stay in the data so “did you find CVE-X?” has an answer, but no estimate is invented for them. Unpriced does not mean the fix cost nothing; it means it cannot be measured from public information. Every total on these dashboards is therefore a floor.
| Reason | What it means | Records |
|---|---|---|
| No public fix found no-public-fix-commit | We looked and could not find the fix anywhere public. | 1,570 |
| Fix not identified by the project fix-not-publicly-attributed | The fix is in the public repository, but the project does not say which commit it is, so naming one would be a guess. | 1,141 |
| Fix lives in another repository out-of-scope-repository | The fix is public, but in a plugin, app or library repository other than the one we measure. | 406 |
| Priced on a sibling record fix-shared-with | Fixed by the same change as another vuln. The effort is priced once, on that record. | 244 |
| Roll-up advisory rollup-advisory | One advisory covering many bugs at once (for example, Firefox's “memory safety bugs fixed in …”). Kept for coverage, not priced. | 110 |
| Fix bundled into a release fix-bundled-in-release | The fix is public but squashed into a release commit with other fixes; any per-vuln split would be invented. | 65 |
| Fixed by configuration only configuration-only-fix | The project's remediation is public, but it changed only build, CI or deployment configuration, which is not written code, so there is nothing to price. | 32 |
| Fixed by documentation only documentation-only-fix | The project's remediation changed documentation and no code, so there is nothing to price. | 16 |
Limitations
- The totals are floors, twice over. Unpriced records add nothing, and the record set itself is incomplete: NVD's product index misses CVEs that NVD has not yet analysed. A second search by product name and reference link found 141 in-window CVEs we do not yet hold (about 1% of the record count).
- Coverage follows disclosure practice. The share of records we can price runs from 0% (7-Zip) to 100% (AshAuthentication, Bouncy Castle (C#), Apache Cassandra, Django, Escargot, Fides, GitPython, Incus, Mastodon, Apache NiFi, OpenBao, OpenFGA, PhpSpreadsheet, Renovate, Spring AI, Tornado, Typebot, Undici, aiohttp, league/commonmark, http4s, Apache HTTP Server, pam_usb, pypdf (formerly PyPDF2), xmldom). Projects that name their fix commits get priced; projects that do not, mostly do not. Compare project totals with that in mind.
- It is a model. The rubric turns a fix's size and class into days; it is not time-tracking data. Where a project's engineers told us which files and lines a fix really was, their answer is used (13 records).
- Step boundaries. 1,679 of 11,634 measured records (14%) sit within 3 lines or 1 file of a lower D1 step, where a small measurement difference moves the cost by one row of the curve. Moving every one of them down a step would lower the total by $2,147,500 (5%). The model is deliberately coarse; the worked example is one of these records.
- Which commit is the fix. For 5,547 priced records (48%) the project itself names the fix — the commit cites the CVE, or the advisory links the commit. Those records carry 46% of the engineer-days. For the rest the match is ours: 28% of the days rest on links drawn from a shared key, such as an issue or pull-request number (inferred-high), and 26% on a choice among a release's candidate commits made by a model, an analyst or a title match (inferred-medium or -low). All of them count in the totals. Each record says which method was used and how confident the match is.
- CVSS is missing on 1,435 priced records, so any severity-versus-cost statistic covers a subset.
Questions
Why can a two-line fix cost several days?
Because D2 and D3 usually outweigh D1. A two-line fix to a JIT compiler scores D1 1 but D2 5 and D3 5: combined 11, 10 days. Most of the work is understanding the bug and proving the fix, not typing it.
Does a critical vuln cost more than a low one?
A little, on average: the median high-severity fix is 2.3333 engineer-days against 2 for low. But not because the fixes are bigger — median written code is about the same from low through high severity. The difference comes from the vulnerability class, which sets D2 and D3, and the classes scored hardest also tend to be rated more severe. Severity is not an input, but part of the relationship is built into the class table. See How cost relates to severity.
What is “tier” in the data?
An exploitability label — T1 primary driver (remote-code-execution-class memory corruption), T2 chain enabler, T3 reachable but limited, T4 hardening — recorded only for Firefox and Tomcat and blank elsewhere. It is not an input to cost and no chart uses it. It is set from the vulnerability class (for Tomcat, class and severity), the same class that sets D2 and D3, so comparing tier with cost mostly compares the rubric with itself.
Does the cost include finding the bug, or shipping the release?
No. Only writing and validating the fix. Discovery, triage, disclosure, release and downstream deployment are outside the model, so the true cost of a vulnerability is higher than the figure shown.
Is AI-assisted coding factored in?
No. The figures are engineer effort without any productivity discount, because nothing in this study measures one.
Can I use a different salary?
Yes. Edit the annual cost in the Overview's cost assumptions. Every dollar figure rescales; engineer-days stay the same, since they do not depend on the rate.
Why is a vuln I know about missing, or shown without a cost?
Missing: see the first limitation — the record set is a floor. Shown without a cost: no public fix could be measured for it. The reasons are listed in What is not priced.
What does “Credited to Anthropic” mean?
The vulnerability's credit names Anthropic or Claude — in the project's own advisory (Firefox), or on the community-maintained list at github.com/patrickmgarrity/Anthropic-Credited-CVEs (checked 2026-10-31, list version e8b88e2a95f2). The credit is often shared with human researchers. 183 records carry it, 176 of them priced (593.5 engineer-days, 2% of the total), in Apache Camel, Apache HTTP Server, Bouncy Castle (C#), Bouncy Castle (Java), Coder, Firefox, Ghost, Gitea, Joomla!, Mastodon, Nuxt, OpenEMR, PCRE2, PostgreSQL, PraisonAI, PrestaShop, Rocket.Chat, Suricata, Symfony, Traefik, Twig, WeKan, Wireshark, ZoneMinder, htslib, libssh2, uutils coreutils. It is a label for cutting the data — the filter on both dashboards — not an input to cost. The list changes often, and the label is refreshed from it from time to time.
Why does one project show far more records than priced records?
Its advisories do not identify the fixing commit, or the fix is not public. 7-Zip has the lowest share: 0% of its records are priced. GitLab publishes no commit reference at all; its records are priced only because we matched each vuln to a commit in its release ourselves, and they are labelled as our inference.