Architecture overview
CAT is a fully client-side single-page application. There is no back-end server, no database, and no user accounts. All data fetching, parsing, and rendering is performed in the user's browser using vanilla JavaScript.
Workers
Public sources
JSON / HTML
in-browser
CORS proxy
Browsers enforce the Same-Origin Policy, which prevents JavaScript from fetching resources from domains it didn't originate from. Since CAT needs to reach different external origins, all requests are routed through a Cloudflare Worker proxy at:
The Worker fetches the target URL server-side and returns the response with permissive
Access-Control-Allow-Origin: * headers, bypassing the browser CORS restriction.
The Worker is stateless and does not store any request data.
Fetch flow
For each CVE, fetches are organised in two phases:
Phase 1 (awaited): CVEList and NVD are fetched simultaneously. These are the primary sources; their data is needed to build the initial card DOM โ assigner, date, base CVSS, initial CWEs, and initial references. The card is inserted into the page once both resolve (or time out).
Phase 2 (parallel): All other sources are fetched simultaneously. Each source updates the card independently via refreshCard(). This allows the page to be useful before all secondary sources finish.
A 400 ms delay is added between cards when processing a batch search, avoiding request storms. The spinning โณ icon on the References row disappears once all Phase 2 queries have settled.
Landing curtain
The landing curtain is a collapsible panel rendered before any CVE search. It aggregates live data from four independent feeds, renders a unified scrolling carousel. All processing is client-side; no dedicated endpoint is involved beyond the CORS proxy.
Data feeds
Four asynchronous fetches are launched in parallel on page load. Each resolves independently and triggers a debounced carousel rebuild (60 ms) to avoid redundant DOM operations when multiple sources settle simultaneously.
Session cache
Once all feeds have loaded, the complete dataset is saved to sessionStorage with a
one-hour TTL. On subsequent page loads within that window, the banner renders instantly from the
cache โ no network requests are made. The cache is cleared when the tab is closed.
Carousel rendering
Cards from all active feeds are interleaved in round-robin order and duplicated to produce a seamless infinite scroll. The scroll speed is kept constant regardless of how many cards are displayed, and the animation pauses on hover. CWE names are resolved from the local database and injected into every chip โ including the duplicated half of the track โ without any additional network request.
Toggling a feed filter chip instantly rebuilds the carousel from already-loaded data, with no new fetch triggered.
Data sources โ detailed reference
The bucket is obtained by taking the first digits depending on the size of the id (e.g. CVE-2024-12345 โ bucket 12xxx or CVE-2024-1345 โ bucket 1xxx).
Extracted fields: Assigner,
Published date,
Descriptions,
Score CVSS,
CWE,
References urls.
This is the primary source. A null response (error network 404 from GitHub) means
the CVE has been reserved but not yet published โ the card shows a "Not in CVEList" warning.
Extracted fields :
Description,
Score CVSS,
CWE,
References urls.
NVD can lag behind CVEList by hours to weeks for newly published CVEs.
Two requests are made in parallel (CSAF VEX and legacy API).
Information extracted :
Description,
Score CVSS,
CWE,
References urls.
The legacy API and the CSAF JSON work in a complementary way (if information is not found in one then we check in the other).
Extraction follows the same CSAF VEX logic as Red Hat:
Description,
Score CVSS,
References urls.
The page is parsed to pick up the description. The HTML is also checked to detect if debian's product is concerns by this vulnerability.
No CVSS data is extracted from Debian (they don't publish their own scores).
The link used to extract the data is the same as the page link.
Extracted fields:
Description,
Score CVSS,
References urls.
Like Red Hat, the fallback URL is used to complete the primary URL, if needed.
Multiple sources are queried in parallel. Priority order for description and CVSS: CSAF > SUG API > CIRCL. This strategy was adopted to maximize the retrieval of information on Microsoft.
Extracted fields:
Description,
Score CVSS,
References urls.
Dot logic: Green if any of the three sources returns data. Black/white (network error) only if both CSAF and SUG API return HTTP 0. Red otherwise.
Example : For the vulnerability CVE-2024-42317, the CSAF file and the CIRCL entry are reachable, but the MSRC SUG API and the MSRC Page do not exist.
Extracted fields:
Description,
Score CVSS.
Only a description is provided by this editor, so it is extracted from HTML tags.
There was an issue with https://www.libreoffice.org/about-us/security/advisories/{cve-lowercase}/ domain, maybe some desynchronisation between regional deploy. cs.libreoffice.org will now be use in case of the first link will redirect to https://www.libreoffice.org/security/.
Extracted fields:
Description,
Score CVSS.
Only a description is extracted from this source. Oracle does not publish machine-readable CVSS data on their individual CVE alert pages.
The full CISA KEV feed is fetched once per browser session and cached in a module-level variable. All subsequent CVE cards look up their ID directly in the in-memory cache โ no extra network request is made.
Extracted fields:
Description,
CWEs,
Reference urls.
The strategy is to check whether the CVE is known to Xen; if so, the name of the associated advisory is retrieved to extract the relevant information.
Only the description is extracted by this editor.
It is possible to request the ENISA directly with a CVE because, the CVE can be an alias with an EUVD identifier.
Extracted fields:
description,
baseScore,
references.
Processing logic
CVSS deduplication
Every time a source returns CVSS data, pushCvss(list, version, data, source) is
called. It computes a (version, score, vector) triplet. If an identical triplet
already exists in list, the source name is appended to its sources[]
array. Otherwise a new entry is created. This means two sources reporting the exact same score
and vector are merged into one badge with both source names.
The vector is normalised to a canonical number of components before comparison: 6 for v2.0, 8 for v3.0/v3.1, 11 for v4.0 (extra metrics like environmental and temporal are stripped). Scores are sorted descending by value; ties broken by version (v4.0 > v3.1 > v3.0 > v2.0).
Description grouping
Two descriptions are considered identical if their
normalizeForComparison() values match โ this function lowercases, collapses
whitespace, normalises unicode quotes/dashes/ellipsis, and strips HTML tags.
The result is a list of {text, sources[]} objects rendered one per row in the
description table. This correctly handles cases where NVD copies CVEList verbatim, or where
a vendor slightly reformats text without changing meaning.
CWE collection
The CWE is information are retrieve in cwe.js.
If any CWE entry is missing a name after collection, one function scrape the CWE detail page from cwe.mitre.org and extract the title from the page's <title> tag.
Reference filtering
Collected references are filtered through two exclusion layers:
(1) Excluded URLs set: All the known source URLs for the current CVE (e.g. the NVD detail page, all sources chip links, all raw data URLs) are pre-computed and excluded. This prevents the References section from being polluted with links that are already clickable via the source chips.
(2) Not-affected domain suppression: If a source is determined to be "not-affected" (e.g. Ubuntu says DNE for all packages), its domain-specific links are filtered out. For example, Ubuntu domain links are suppressed if Ubuntu is not-affected. This avoids noisy references pointing to Ubuntu pages that merely confirm non-affectedness.
EPSS scoring
EPSS scoring is retrieved from first.org/epss.
One function queries the official EPSS API and retrieves the latest daily score, then is compare to the score a month ago, depending of evolution observed an indication will show the trend.
Environmental requirements
A calculator holds values for all overridable metrics (CR, IR, AR plus
all modified base metrics). It is persisted in localStorage
and loaded on startup. Any change recomputes an environmental sub-score.
Score computation per version:
- CVSS v2.0 : applies CR/IR/AR weights to the base CIA metrics via the NIST adjusted impact formula, then recomputes exploitability. Output rounded to one decimal.
- CVSS v3.0 / v3.1 : Modified metrics (MAV, MAC, MPR, MUI, MS, MC, MI, MA) override their base counterparts when set. The modified impact sub-score uses CR/IR/AR weights; scope change is respected. Output rounded via the CVSS round-up function.
- CVSS v4.0 : Computes six severity levels (EQ1โEQ6) from the vector, looks up the base score in a 218-entry table, then interpolates within each level to produce a precise score. Supports all v4.0 modified metrics.
UI โ compact panel vs. full modal. The Options panel exposes only the three
Security Requirements (CR/IR/AR) plus a ๐งฎ button that opens the full modal. The modal
renders all four groups via :
Security Requirements, Modified Base โ Common (v3+v4),
Modified Base โ v3.x, and Modified Base โ v4.0.
All temporal and unset modified metrics are left as X / ND.
CAPEC associations
CAPEC (Common Attack Pattern Enumeration and Classification) entries are sourced from the local
data/cwe.js database. Each CWE entry in the database carries an optional c
array of CAPEC IDs that are known to exploit that weakness.
When CWE chips are rendered, the capecs array from
the resolved DB entry is read. A single CAPEC is shown as a direct link chip below the CWE row. When multiple
CAPECs exist, they are collapsed behind a N CAPECs โพ toggle button to keep the UI compact.
Each chip links to the corresponding CAPEC detail page on capec.mitre.org. No additional network
request is made โ all data comes from the pre-built local database.
State management
CAT maintains two layers of client-side state for CVE results: a persistent list of CVEs in
localStorage, and in sessionStorage.
Neither layer requires a backend.
Data cache
CVE data is stored exclusively in localStorage, with a maximum of 25 CVE IDs in the master list (cat_cves). When a 26th CVE is added, the oldest ID and its associated data entry are evicted. All entries share a 7-day TTL.
The landing curtain feed data (CISA KEV, ENISA lists, NVD latest, EPSS top 5, EPSS batch map) is cached separately in sessionStorage under the key cat_curtain with a 1-hour TTL. On page reload, the curtain renders instantly from this cache โ all network requests are skipped until the TTL expires or the tab is closed.
LRU eviction
CVE data entries can be large (rich CSAF payloads, full Ubuntu JSON). If localStorage quota is exceeded when saving a card, the Least Recently Used (LRU) strategy evicts the oldest cached entry:
- Attempt to write the new entry.
- If a
QuotaExceededErroris caught, scan allcat_cve_*keys inlocalStorageand find the one with the lowest timestamp (oldest write time). - Remove that entry and retry the write.
- Repeat up to 30 times until the write succeeds or no evictable entries remain.
cat_cve_*) but never touches the master ID list (cat_cves) โ all 25 CVE IDs remain listed even if their cached data was evicted. A fresh fetch is triggered the next time the card is restored.
Cache keys reference
All keys written by CAT:
UI architecture
Shareable permalink
Each CVE card exposes a share button (๐) inline with the CVE title. Clicking it constructs a URL of the form
index.html?cves=CVE-XXXX-XXXXX (comma-separated for multiple IDs) and writes it
to the clipboard via the Clipboard API.
When CVEs come from the URL, the localStorage restore
is skipped to prevent duplicate cards.
Source chip collapse
A chip whose dot has settled to a non-ok state
(dot-fail, dot-notaffected, dot-networkerror) receives
data-collapsed="true" and is hidden. A button showing
+N โพ is injected at the end of the row; clicking it toggles the
chips-expanded
to show or hide the collapsed chips.
Source filter groups
The "Visible sources" panel is divided into two sub-groups: Databases (CVEList, NVD, ENISA) and Editors (all vendor sources). All sources are fully toggleable.
CVE navigator
The navigator is a dropdown widget embedded directly to the left of the search form. Each CVE in the list carries a colour corresponding to its max CVSS score. The list is rebuilt every time a card is added, deleted, or sorted.
Field visibility
Field visibility is implemented entirely in CSS via body-level classes. When a field is disabled, it masks this section on all cards simultaneously โ no manipulation of individual cards is required.
Smart refresh
The โป refresh button re-queries all sources that returned dot-fail (red) or dot-networkerror (black/white). A sessionSave call at the end persists the refreshed state to cache.
File structure
โโ Project root โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
shared.css CSS variables, navigation, buttons โ shared across all pages
app.js All JavaScript logic for the main page
index.html Primary CVE search interface
user-guide.html User guide
technical-doc.html Technical documentation
changelog.html Version history
contact.html Author information
data/cwec_v4.19.1.xml Official CWE catalog from cwe.mitre.org
data/generate_cwe_js.py Converts a CWE XML catalog (cwec_vX.Y.Z.xml) into data/cwe.js
data/cwe.js Formatted CWE dataset used by the application
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Splitting into separate files provides several practical benefits: the main page's app.js can be edited without touching HTML; shared.css ensures all pages share the same design tokens and navigation without copy-paste; and the three documentation pages are purely HTML/CSS with no dependency on app.js, so they load immediately.