Scoring real breach risk: likelihood × impact, not raw CVE counts.
How Pythorix ranks findings by likelihood times impact, why unverified exploit claims cap at Medium, and the false-positive lessons behind the severity gate.
Open a typical vulnerability report and the first thing you see is a count. Two hundred and twelve findings, forty-one High. The number is accurate, in the sense that the scanner emitted that many records. It is also almost useless for deciding what to fix first, because it treats a missing security header on a marketing page and an unauthenticated admin panel as the same kind of thing.
This post explains how the Pythorix ranker works instead, why we cap severities that cannot be verified, and the engineering lessons we learned from our own false positives.
Why do vulnerability counts mislead?
Most scanners are built to detect patterns. A pattern match is cheap, so the economics push toward emitting more of them, and severity is assigned by the pattern rather than by what the pattern means on this particular target. The outcome is predictable: reports where real exposure is buried under volume, and teams that learn to ignore the scanner.
We have seen all of the classic over-fires in our own history. Fingerprinting a framework version and reporting every CVE ever filed against it, whether or not the vulnerable code path is reachable. Flagging a .env path because it returned 200, when the 200 was a single-page app returning its index for every route. Calling a 403 on /admin a finding. Treating a generic error string as evidence of injection.
How does likelihood × impact scoring work?
Every finding in Pythorix carries two independent scores, and the severity you see is derived from their product.
Likelihood
How plausible is it that an attacker actually exploits this, on this target, today? The inputs are concrete:
- Reachability. Is the vulnerable surface on the public internet with no auth in front of it, or did we infer it from a version string?
- Verification status. Did the scanner reproduce the behaviour, or match a pattern? This is the single heaviest weight.
- Preconditions. Does exploitation need a valid session, a specific user action, or a second finding to chain with?
- Exploit maturity. For known CVEs, whether public exploit code exists and whether the vulnerable path is confirmed present.
- Defensive signals. Rate limiting, WAF responses, and authentication challenges observed during the scan reduce likelihood.
Impact
If it is exploited, what does the attacker get?
- Data class. Credentials and secrets, personal data, business data, or nothing of value.
- Blast radius. One user, one tenant, or every tenant on the platform.
- Privilege gained. Read, write, or execute. Anonymous, user, or admin.
- Chaining potential. Whether this finding is a stepping stone in a known attack path to something worse.
| Likelihood | Impact | Resulting severity |
|---|---|---|
| Verified, unauthenticated, public | Credentials or every tenant's data | Critical |
| Verified, needs a valid session | One user's data or admin write | High |
| Pattern match only, no reproduction | Any | Medium (capped by the gate) |
| Verified, public | Information only, no data access | Low |
| Inferred from a version string | Unknown | Info |
The product of the two maps onto Critical, High, Medium, Low, and Info. A finding can be high-impact and still land at Medium because likelihood is low. That is the correct answer more often than most reports admit.
What is the mandatory severity gate?
One rule sits above everything else in the ranker, and no module can bypass it. If a finding belongs to an exploit class, meaning it claims the target can be actively compromised rather than merely observed to be misconfigured, it may not carry High or Critical unless it carries a verification signal.
A verification signal is evidence the scanner produced by actually exercising the vulnerability: a canary that came back, a differential response that proves an injection point, an authenticated-only resource fetched without authentication, a secret that was confirmed live. Pattern matches, version inferences, and suggestive error messages are not verification.
This single rule removed the majority of the noise from our reports. It also changed how we build modules: a detector that cannot verify its own claim is now expected to try, which pushed us to add safe reproduction steps to classes of findings that used to be pure pattern matching.
What did our own false positives teach us?
We keep a running list of false-positive classes we have fixed, and it is one of the most valuable documents in the codebase. A few of the lessons that generalise:
- A 200 is not a finding. Single-page apps, catch-all routes, and friendly 404 pages all return 200 with content. Compare the response against a known-missing path before believing anything.
- Version strings are hints, not proof. Report the inference as Info and let a verified check raise it.
- Headers tell you about one response. CDN edges, error pages, and redirects often carry different headers than the application. Sample several paths before declaring a header missing.
- Errors are not injection. A stack trace means the app has an error-handling gap. It does not mean user input reached a dangerous sink.
- Context matters for secrets. A string that looks like an API key inside a test fixture or a documentation page is not the same as one inside a production bundle.
How do findings become attack paths?
Scoring individual findings is only half the job. Attackers do not exploit a single item from a list; they chain. A readable source map plus an internal endpoint URL plus a missing authentication check is a breach, even though none of the three would individually rate above Medium.
Pythorix builds these chains explicitly. Findings are nodes, the relationships the scanner can establish between them are edges, and an attack path is a walk from an anonymous starting point to an outcome that matters. The Real Breach Risk score on your dashboard is the maximum over the paths we found, not a sum over the findings, because that is what the worst day actually looks like.
What does this mean for your report?
- The top of the report is the thing most likely to cause an incident, not the thing with the scariest label.
- Every High or Critical has evidence you can replay.
- Mediums are worth reading: some of them are unverified Highs waiting for a human with context.
- A low count is not a clean bill of health. The surface we could not reach is not in the report.
Frequently asked questions
What is the difference between CVSS and risk-based vulnerability scoring?
CVSS rates the intrinsic severity of a vulnerability class in isolation. Risk-based scoring adds context about this target: whether the vulnerable path is reachable, whether the scanner verified it, what data sits behind it, and what else it chains with. Two findings with the same CVSS score can have very different real-world risk.
Why does my scanner report so many false positives?
Most scanners assign severity by pattern rather than by verification. A 200 response, a version string, or an error message is treated as proof. Requiring a reproduction step before a finding can be High or Critical removes most of the noise.
What is an attack path in vulnerability management?
A chain of individually modest findings that together give an attacker a path from anonymous access to a damaging outcome, such as a leaked internal URL plus a missing auth check plus a readable data export. Attack path analysis ranks these chains rather than individual findings.
Should unverified findings be ignored?
No. They should be capped at Medium, kept with their evidence, and reviewed by someone with context. Some are real Highs that the scanner could not safely reproduce.