CVE panel 2.0: CISA KEV, EPSS and exposure context - #333
Open
Eales wants to merge 5 commits into
Open
Conversation
Shodan gives us bare CVE ids, which says nothing about whether a vulnerability is actually being exploited. This adds an enrichment step against two free, key-less feeds: - CISA KEV, for vulnerabilities confirmed exploited in the wild - FIRST EPSS, for the probability of exploitation in the next 30 days It also maps each CVE back to the exposed service that reported it, and derives a patch priority from the KEV-first ordering CISA recommends. Both feeds are best-effort: if either is unreachable the host result is returned unchanged, just with less context. Adds unit tests covering the parsing, merging and triage rules, run with `yarn test` via the built-in Node test runner (no new dependencies).
The panel listed CVEs as flat links to NVD, leaving the reader to work out which ones matter. Each entry now shows its CVSS score, whether CISA lists it as actively exploited, its EPSS probability and percentile, the exposed service it was found on, and a resulting patch priority, sorted worst-first. A summary line reports how many CVEs are in the KEV catalog and the highest EPSS score on the host. Older API instances that return plain CVE ids still render, and the card says so when either feed was unavailable rather than showing a misleading "not listed".
β Deploy Preview for web-check ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
The chip was built as module + version + port, which produced "HTTPS 2 :443" and "SSH 9.8 :22" on a real host: the version Shodan reports belongs to the product behind the service (Apache 2, OpenSSH 9.8), not to the protocol. The chip now reads "HTTPS :443", and the full banner plus transport moves to the hover title.
Changing the shape of parseShodanResults().vulns broke the server-info analyzer, which gated on Array.isArray(d.vulns). It silently returned nothing, so a host with CVEs β including one in the KEV catalog β showed no CVE entry in the Advisory section at all. The analyzer now reads the enrichment through asCveIntel and reports each CVE at the severity its evidence supports: KEV-listed as critical, high EPSS as an issue, and the long tail as warnings and info, instead of filing 25 mostly-dormant CVEs as equally critical. Nothing is dropped β every CVE is still listed under one of the four headings. When there is no evidence to rank against, because the API is an older build or CISA and FIRST are both unreachable, it falls back to the original single critical finding rather than quietly downgrading everything to info.
The feed timeout was 20s, but the sample config suggests running the API with PUBLIC_API_TIMEOUT_LIMIT=25000. A hanging CISA or FIRST request could therefore exhaust the budget for the whole Shodan check and take the host name and server info cards down with it, neither of which has anything to do with CVE enrichment. The timeout drops to 6s, comfortably above the ~500ms a cold lookup actually costs. A failed catalog fetch is also cached now, for a shorter interval than a successful one, so an outage costs one timeout every five minutes rather than one on every single request.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
The vulnerabilities panel currently lists whatever CVEs Shodan associates with the host, each linked to its NVD record. That tells you a vulnerability exists, but not whether anyone is actually exploiting it β so a decorative CVSS 9.8 with no real-world activity looks exactly as alarming as something under active attack.
This PR enriches each CVE with two free, key-less feeds and reworks the card around the result:
vulnsthat Shodan already returns and that we were previously discarding.Priority rules
CISA's guidance is to work the KEV catalog first and use EPSS to rank what's left; CVSS says how bad exploitation would be, not how likely it is, so it never outranks evidence of actual exploitation.
The effect is to decouple the verdict from raw severity in both directions: a CVSS 5.3 at 95% EPSS is escalated to Patch soon, while a CVSS 9.8 with a negligible EPSS falls back to Schedule (covered by the
computePriority de-escalates a high CVSS with negligible exploit probabilitytest).Example
Against a fixture of four real CVEs, resolved through the live feeds (KEV catalog
2026.08.07, EPSS scored2026-08-09):Note the third row: SWEET32 scores a middling 5.3 on CVSS, but sits in the 99.9th EPSS percentile, so it outranks the nginx heap-write below it.
Implementation notes
api/_common/cve-intel.js, called fromapi/shodan.jsβ no new API route, no extra Shodan quota, and no new dependencies.buildCveIntelusesPromise.allSettled, andshodan.jsfalls back to the raw host result if enrichment throws, so a CISA or FIRST outage can never fail the check. The card says which feed was unavailable rather than showing a misleading "Not listed".asCveIntelreconstructs the shape and marks the entries unranked.Tests
This adds the first unit tests to the repo, using the built-in Node test runner β no new dependencies, no config file. 31 tests over the parsing, merging, sorting and triage rules, including the malformed-input and feed-unavailable paths.
I've wired a
π§ͺ Unit Testsjob into.github/workflows/ci.ymlalongside the existing lint/typecheck jobs, and mentionedyarn testin the developer setup section of the README. Happy to drop either if you'd rather keep the CI surface as it is.Verification
yarn testβ 31/31 passyarn lintβ cleanyarn typecheck(astro check) β 0 errors, 0 warnings, 0 hintsyarn buildβ completesprettier --checkon every touched file β clean2026.08.07, EPSS2026-08-09), plus a rendered screenshot of the card against enriched fixture dataNotes / open questions
cve-intel.jsβ easy to tune if you'd prefer a different cut.