How CWE Explorer links CVEs to CWEs¶
This document explains where the data comes from when you give cwex a CVE
identifier, how the links between a CVE and its CWEs are built and merged, and how
those CWEs are placed in MITRE's View 1000 hierarchy. Figures are from the CWE 5.0
catalog (2026-10-28).
The short version¶
A CWE is a type of weakness ("Deserialization of Untrusted Data"). A CVE is one concrete vulnerability in one product ("Log4Shell in Apache Log4j2"). Several parties state which CWE types a CVE is an instance of. CWE Explorer collects those statements from three places, merges them into one list of links (each one labeled with who made it), and then draws the CWEs inside the View 1000 hierarchy, which comes from the CWE catalog.
flowchart LR
subgraph online["Online, cached for 7 days"]
ORG["CVE.org API<br/>official CVE record"]
NVD["NVD API 2.0<br/>NIST analysis"]
end
subgraph offline["Offline: the CWE catalog"]
XML["cwec_latest.xml<br/>CWE 5.0"]
end
ORG -- "CNA and ADP problemTypes<br/>CVSS, description, dates" --> MERGE
NVD -- "the NVD's own weaknesses<br/>CVSS" --> MERGE
XML -- "Observed_Examples<br/>(CVEs cited by each CWE)" --> MERGE
MERGE["cve.py<br/>merge the links<br/>and keep their sources"] --> REC["CVE record<br/>links + CVSS"]
XML -- "ChildOf links of View 1000" --> HIER["Hierarchy<br/>957 weaknesses"]
REC --> PLACE{"Is the CWE in<br/>View 1000?"}
HIER --> PLACE
PLACE -- yes --> OUT["Tree, TUI, web graph, JSON:<br/>lineages up to the pillars"]
PLACE -- no --> NOTE["Listed with an explanation:<br/>grouping, deprecated, NVD-CWE-noinfo…"]
How the two catalogs relate¶
The CVE and CWE catalogs are separate and are maintained by different people. The only links between them are statements of the form "CVE X is an instance of CWE Y", and the link always goes from the CVE to the CWE, with one exception: the CWE catalog itself cites some CVEs as observed examples of a weakness, which is the same relationship seen from the other side.
erDiagram
CVE ||--o{ LINK : "is linked by"
LINK }o--|| CWE_REF : "points to"
LINK ||--|{ SOURCE : "is asserted by"
CWE_REF |o--o| WEAKNESS : "resolves to, if in View 1000"
WEAKNESS }o--o{ WEAKNESS : "ChildOf (View 1000)"
WEAKNESS ||--o{ OBSERVED_EXAMPLE : "cites"
OBSERVED_EXAMPLE }o--|| CVE : "refers to"
CVE {
string id "CVE-2021-44228"
string state "PUBLISHED or REJECTED"
date published
string assigner "CNA short name"
list cvss "score, severity, version, source"
}
LINK {
string ref "CWE-502, or NVD-CWE-noinfo"
int cwe "null for NVD-CWE-* values"
list sources "apache (CNA), NVD, ..."
}
SOURCE {
string name "CNA, ADP, NVD or CWE catalog"
}
WEAKNESS {
int id
string abstraction "Pillar, Class, Base, Variant, Compound"
string status "Stable, Draft, Incomplete..."
}
Things worth noticing in this model:
- It is many to many. A CVE can have several CWEs (Log4Shell has five), and a
CWE has thousands of CVEs. The links described here start from a CVE. The other
direction, every CVE of a CWE, is asked of the NVD alone with
cwex 79 --cves(see CVEs of a CWE): it gives a count and the most recent CVEs, not links merged from every source. - The link points to a CWE reference, not always to a weakness. The reference
may be a grouping (formerly a category), a view, a deprecated entry, or one of the
NVD's placeholders (
NVD-CWE-noinfo,NVD-CWE-Other). Those links are kept and explained, but they have no place in the hierarchy. - Each link remembers every source that asserts it. That is the information you need to judge a link, because sources often disagree.
Data sources¶
Who the CNAs, the ADPs, the NVD and the CWE catalog are, and how they relate, is explained in The CVE ecosystem. This section covers what the tool reads from each source.
| Source | Endpoint | What CWE Explorer reads | Offline |
|---|---|---|---|
| CVE.org record | https://cveawg.mitre.org/api/cve/{id} (CVE JSON 5) |
State, publication date, CNA, title, description, CWEs of the CNA and of each ADP, CVSS | No (cached) |
| NVD | https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={id} |
CWEs assigned by NVD analysts, NVD's CVSS, and the description, dates and state if CVE.org did not answer | No (cached) |
| CWE catalog | https://cwe.mitre.org/data/xml/cwec_latest.xml.zip (already downloaded for the hierarchy) |
CVEs cited as observed examples, and the ChildOf links of View 1000 |
Yes |
| FIRST | https://api.first.org/data/v1/epss?cve={id} |
The EPSS score: probability of exploitation in the next 30 days, percentile and date | No (cached for a day) |
| NVD, by weakness | https://services.nvd.nist.gov/rest/json/cves/2.0?cweId=CWE-{id} |
For --cves: how many CVEs list the CWE, and the most recent ones |
No (cached for a day) |
CVE.org: the official record¶
The CVE record is published by the CNA (CVE Numbering Authority) that assigned
the identifier, usually the vendor. ADPs (Authorized Data Publishers), such as
CISA's Vulnrichment program, can add their own container to the same record. Both
may include problemTypes with CWE identifiers:
{
"cveMetadata": {
"cveId": "CVE-2021-44228",
"state": "PUBLISHED",
"assignerShortName": "apache",
"datePublished": "2021-12-10T00:00:00.000Z"
},
"containers": {
"cna": {
"problemTypes": [{"descriptions": [
{"type": "CWE", "lang": "en", "cweId": "CWE-502",
"description": "CWE-502 Deserialization of Untrusted Data"}
]}]
},
"adp": [{"providerMetadata": {"shortName": "CISA-ADP"}, "problemTypes": ["…"], "metrics": ["…"]}]
}
}
| Field | Used as |
|---|---|
cveMetadata.state, datePublished, assignerShortName |
State, publication date and CNA |
containers.cna.title, descriptions[lang=en] |
Title and description (rejectedReasons for rejected CVEs) |
containers.cna.problemTypes[].descriptions[].cweId |
Links with source <CNA> (CNA), for example apache (CNA) |
containers.adp[].problemTypes[]…cweId |
Links with the ADP's short name as source, for example CISA-ADP |
containers.{cna,adp[]}.metrics[].cvssV4_0, cvssV3_1, cvssV3_0, cvssV2_0 |
CVSS scores, with the same sources |
containers.adp[].metrics[].other with type: "ssvc" |
CISA's SSVC assessment: its options, version and timestamp |
containers.adp[].metrics[].other with type: "kev" |
The KEV entry: dateAdded and a link to CISA's catalog |
NVD: NIST's analysis¶
The NVD republishes every CVE and adds its own analysis. Its weaknesses list mixes
the NVD's own CWEs with copies of what the CNA and the ADPs said, distinguished by
source:
"weaknesses": [
{"source": "security@apache.org", "type": "Secondary",
"description": [{"value": "CWE-20"}, {"value": "CWE-400"}, {"value": "CWE-502"}]},
{"source": "nvd@nist.gov", "type": "Secondary",
"description": [{"value": "CWE-917"}]}
]
Only the entries whose source is nvd@nist.gov are taken, with the source NVD. The
others are copies of the CVE.org record, which is read directly. If CVE.org could not
be reached, they are used too, labeled <source> (via NVD). The NVD's own CVSS
metrics (metrics.cvssMetricV40, V31, V30, V2 with source nvd@nist.gov) are
added with the source NVD. The NVD also copies CISA's KEV data (cisaExploitAdd,
cisaActionDue, cisaVulnerabilityName, cisaRequiredAction), which completes the
KEV entry from CVE.org with the deadline and CISA's name for the vulnerability.
The CWE catalog: observed examples¶
Many CWE entries list real CVEs that illustrate the weakness. When the catalog is parsed, each of those citations is indexed as CVE → CWEs that cite it:
<Weakness ID="917" Name="Improper Neutralization of Special Elements used in an Expression Language Statement" …>
<Observed_Examples>
<Observed_Example>
<Reference>CVE-2021-44228</Reference>
<Description>Product does not neutralize ${xyz} style expressions, allowing remote
code execution. (log4shell vulnerability in log4j)</Description>
<Link>https://www.cve.org/CVERecord?id=CVE-2021-44228</Link>
</Observed_Example>
</Observed_Examples>
</Weakness>
The catalog cites 2,202 distinct CVEs (from 1999 to 2026) in 3,172 citations spread
over 593 weaknesses, all of them in View 1000. Most of those CVEs (1,485) are cited by
a single CWE. The record is CVE-2014-1266 (Apple's "goto fail"), cited by 10 CWEs.
This index is stored with the catalog cache (catalog.json), so it works offline and
is also embedded in the web page.
The same XML provides the hierarchy: only Related_Weakness elements with
Nature="ChildOf" and View_ID="1000" are used. CWE-917, for example, also declares
ChildOf CWE-74 in view 1003 (the one the NVD uses), which is ignored.
Building the list of links¶
lookup_cve() in cwe_explorer/cve.py runs these steps. The browser does the same
in fetchCve() and lookupCve() (see In the browser).
sequenceDiagram
autonumber
participant T as cwex
participant C as Cache<br/>cve/ID.json
participant O as CVE.org API
participant N as NVD API
participant X as CWE catalog
T->>C: read the saved copy
alt copy younger than 7 days and no --update
C-->>T: saved record
else no copy, old copy or --update
T->>O: GET /api/cve/{id}
O-->>T: CVE record, 404, or network error
T->>N: GET /cves/2.0?cveId={id}
N-->>T: analysis, no results, or network error
alt both answered and at least one knows the CVE
T->>C: save the merged record
else a source failed
Note over T: use what arrived, add a note, do not save
end
end
T->>X: CWEs whose observed examples cite the CVE
X-->>T: links with source "CWE catalog (observed example)"
cwex builds the list of links for a CVE: a cached copy when it is
recent enough, otherwise CVE.org and the NVD, and always the catalog's observed
examples.Merge rules¶
- Normalization. A value is a link if it starts with
CWE-<number>: thecweIdfield, or the text of a description such asCWE-79 Improper Neutralization….NVD-CWE-noinfoandNVD-CWE-Otherare kept as special links. Anything else, such asn/aor free text, is ignored. - One link per CWE. If several sources name the same CWE, it is a single link
whose
sourceslist grows (CWE-917 · from NVD, CWE catalog (observed example)). - Source order. Links appear in the order they are first found: the CNA, then each ADP in record order, then the NVD, then the CWE catalog. The official links therefore come before the observed examples.
- NVD entries. Only the NVD's own (
nvd@nist.gov), unless CVE.org was unreachable (see above). - CVSS. One score per version and source, rounded to one decimal, with the severity capitalized. The web graph labels the CVE node with the newest version available (4.0 over 3.1 over 3.0 over 2.0).
- Text fields. State, dates and description come from CVE.org; the NVD only fills them in when CVE.org did not answer.
Placing the links in View 1000¶
Each link is then resolved against the hierarchy:
- The CWE is in View 1000 (957 weaknesses, including the deprecated CWE-377, which still has children): it becomes one of the CVE's linked CWEs.
- It is not: the link stays in the list with an explanation, the same one you get when you query that ID directly. For example, "CWE-16 (Configuration) is a grouping (formerly a Category), not a weakness, so it is not part of View 1000", or "NVD-CWE-noinfo: the NVD found insufficient information to assign a CWE".
The linked CWEs are drawn together:
- Tree and interactive explorer. The lineages of every linked CWE are merged into
a single tree (
build_rows()with several focuses), so shared ancestors appear only once. Linked CWEs are marked with◀. If a linked CWE is an ancestor of another one, as CWE-74 is of CWE-917 in Log4Shell, the tree follows the path down to the second one. - Web graph. The CVE is one more node (internal ID
-1), whose parents are its linked CWEs. The layered layout therefore places it one column to the right of the deepest one, joined by dotted edges. The panel of each linked CWE links back to the CVE. --ancestors/--descendants. These run once for each linked CWE.
By default a CVE is shown with --depth 0: the lineages of its CWEs and nothing
below them, so the whole picture fits. The children of each CWE, which are the more
specific alternatives, are one +N click or one --depth 1 away.
Two worked examples¶
Log4Shell (CVE-2021-44228)¶
| Link | apache (CNA) | CISA-ADP | NVD | CWE catalog |
|---|---|---|---|---|
| CWE-502 Deserialization of Untrusted Data (Base) | ✓ | |||
| CWE-400 Uncontrolled Resource Consumption (Class) | ✓ | |||
| CWE-20 Improper Input Validation (Class) | ✓ | |||
| CWE-917 Improper Neutralization of Special Elements used in an Expression Language Statement (Base) | ✓ | ✓ | ||
| CWE-74 Improper Neutralization of Special Elements in Output Used by a Downstream Component (Class) | ✓ |
CISA's container adds a CVSS 3.1 score (10.0 Critical) but no CWE. The NVD's own
analysis disagrees with Apache's: it points to CWE-917, expression language
injection, which describes the ${…} lookups the attack abuses. The CWE catalog cites
the CVE under CWE-917 and under its ancestor CWE-74. Merged in View 1000:
flowchart LR
P664["Pi CWE-664<br/>Improper Control of a Resource<br/>Through its Lifetime"] --> C913["Cl CWE-913<br/>Improper Control of Dynamically-<br/>Managed Code Resources"]
C913 --> B502["Ba CWE-502<br/>Deserialization of Untrusted Data"]
P664 --> C400["Cl CWE-400<br/>Uncontrolled Resource Consumption"]
P707["Pi CWE-707<br/>Improper Neutralization"] --> C20["Cl CWE-20<br/>Improper Input Validation"]
P707 --> C74["Cl CWE-74<br/>Improper Neutralization of Special<br/>Elements in Output Used by a<br/>Downstream Component"]
C74 --> C77["Cl CWE-77<br/>Improper Neutralization of Special<br/>Elements used in a Command"]
C77 --> B917["Ba CWE-917<br/>Improper Neutralization of Special<br/>Elements used in an Expression<br/>Language Statement"]
B502 -. "apache (CNA)" .-> CVE(["CVE-2021-44228<br/>Log4Shell · 10.0 Critical"])
C400 -. "apache (CNA)" .-> CVE
C20 -. "apache (CNA)" .-> CVE
B917 -. "NVD, catalog" .-> CVE
C74 -. "catalog" .-> CVE
The picture shows at a glance what the list of links does not: Apache's three CWEs belong to two different pillars, and the NVD's CWE-917 sits at the bottom of the injection branch, three levels below the pillar.
Heartbleed (CVE-2014-0160)¶
| Link | redhat (CNA) | CISA-ADP | NVD | CWE catalog |
|---|---|---|---|---|
| CWE-125 Out-of-bounds Read (Base) | ✓ | ✓ | ✓ | |
| CWE-126 Buffer Over-read (Variant) | ✓ | |||
| CWE-130 Improper Handling of Length Parameter Inconsistency (Base) | ✓ | |||
| CWE-240 Improper Handling of Inconsistent Structural Elements (Class) | ✓ |
The CNA's record says n/a, so the official link comes only from CISA and the NVD.
The catalog cites Heartbleed under four CWEs. One of them, CWE-126, is a child of the
official CWE-125 (and also of CWE-788). Two others, CWE-240 and its child CWE-130,
sit in a different pillar. CWE-240 alone has three lineages: two through CWE-228,
from the pillars CWE-707 and CWE-703, and one directly from CWE-707.
flowchart LR
P664["Pi CWE-664"] --> C118["Cl CWE-118<br/>Incorrect Access of<br/>Indexable Resource"]
C118 --> C119["Cl CWE-119<br/>Improper Restriction of Operations<br/>within the Bounds of a Memory Buffer"]
C119 --> B125["Ba CWE-125<br/>Out-of-bounds Read"]
C119 --> B788["Ba CWE-788<br/>Access of Memory Location<br/>After End of Buffer"]
B125 --> V126["Va CWE-126<br/>Buffer Over-read"]
B788 --> V126
P707["Pi CWE-707"] --> C228["Cl CWE-228<br/>Improper Handling of<br/>Syntactically Invalid Structure"]
C228 --> B237["Ba CWE-237<br/>Improper Handling of<br/>Structural Elements"]
B237 --> C240["Cl CWE-240<br/>Improper Handling of Inconsistent<br/>Structural Elements"]
P703["Pi CWE-703"] --> C228
P707 --> C240
C240 --> B130["Ba CWE-130<br/>Improper Handling of Length<br/>Parameter Inconsistency"]
B125 -. "CISA-ADP, NVD, catalog" .-> CVE(["CVE-2014-0160<br/>Heartbleed · 7.5 High"])
V126 -. "catalog" .-> CVE
B130 -. "catalog" .-> CVE
C240 -. "catalog" .-> CVE
Caching and working offline¶
| Situation | Result |
|---|---|
A copy younger than 7 days exists, and no --update |
The saved copy, without any network access. Observed examples always come from the current catalog. |
| CVE.org and the NVD both answer, and at least one knows the CVE | A fresh record, saved for 7 days |
| One of them fails | What the other returned, plus a note. It is not saved, so the next run tries again. |
| Both fail, and an older copy exists | The older copy, plus a note with the date it was saved |
| Both fail, there is no copy, and the catalog cites the CVE | Only the catalog's links, plus a note for each source that failed |
| Both fail, there is no copy, and the catalog does not cite it | Error: could not reach CVE.org or the NVD, and the CWE catalog does not cite it |
| Both answer that the CVE does not exist | Error: does not exist in CVE.org or the NVD (unless the catalog cites it) |
Copies are saved as ~/.cache/cwe-explorer/cve/<ID>.json (or under CWEX_CACHE).
EPSS scores are saved apart, in epss/<ID>.json, for one day, because FIRST
recalculates them daily; when FIRST cannot be reached, an older score is shown with
a note.
Without an API key, the NVD allows 5 requests every 30 seconds. Set NVD_API_KEY to
send a key with every NVD request (see NVD API key). If the
NVD rejects the key it answers 404, which the tool reports as a failed source instead
of taking it for "unknown CVE": for a CVE it does not know, the NVD answers 200 with
no results.
In the browser¶
The web page applies the same rules in JavaScript (fetchCve(), lookupCve() and
setCve() in cwe_explorer/web/viewer.js). It can therefore look up any CVE,
not only the one it was generated for:
- Where to type it. In the search box, or in the address (
#CVE-2014-0160, a link you can share). - How it is fetched. CVE.org, the NVD and FIRST allow requests from any origin (CORS), so the page queries them directly, in parallel. The CVEs in the NVD section of a CWE asks the NVD by weakness in the same way.
- Offline. The catalog's observed-example index is embedded in the page (about 55 KB), so links from the catalog are available without a connection, as in the terminal.
- Caching. Lookups made from the page are not cached between visits.
Output for other programs¶
cwex CVE-… --ancestors --json (or --descendants) returns the merged record and
one result per linked CWE. Every field is described in the
JSON reference.
{
"schema": 1,
"type": "cve-relatives",
"catalog": {"name": "CWE", "version": "5.0", "date": "2026-10-28", "view": "1000", "view_name": "Complete Weakness Hierarchy"},
"mode": "ancestors",
"depth": 1,
"cve": {
"id": "CVE-2021-44228",
"state": "PUBLISHED",
"published": "2021-12-10",
"assigner": "apache",
"title": "Apache Log4j2 JNDI features do not protect against attacker controlled LDAP and other JNDI related endpoints",
"description": "Apache Log4j2 2.0-beta9 through 2.15.0 …",
"cvss": [
{"version": "3.1", "score": "10.0", "severity": "Critical",
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", "source": "CISA-ADP"}
],
"mappings": [
{"ref": "CWE-502", "cwe": 502, "sources": ["apache (CNA)"], "in_view": true, "note": null},
{"ref": "CWE-917", "cwe": 917, "sources": ["NVD", "CWE catalog (observed example)"], "in_view": true, "note": null}
],
"notes": [],
"kev": {"date_added": "2021-12-10", "due_date": "2021-12-24",
"name": "Apache Log4j2 Remote Code Execution Vulnerability", "required_action": "…",
"url": "https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2021-44228",
"sources": ["CISA-ADP", "NVD"]},
"ssvc": {"options": [["Exploitation", "active"], ["Automatable", "yes"], ["Technical Impact", "total"]],
"version": "2.0.3", "date": "2025-02-04", "source": "CISA-ADP"},
"epss": {"score": 0.99999, "percentile": 1.0, "date": "2026-11-03"},
"url": "https://www.cve.org/CVERecord?id=CVE-2021-44228",
"nvd_url": "https://nvd.nist.gov/vuln/detail/CVE-2021-44228"
},
"weaknesses": [
{
"schema": 1, "type": "relatives", "id": 502, "cwe": 502,
"name": "Deserialization of Untrusted Data", "abstraction": "Base",
"mode": "ancestors", "depth": 1, "count": 1, "note": null,
"results": [
{"id": 913, "name": "Improper Control of Dynamically-Managed Code Resources", "distance": 1,
"via": [502], "primary_parent": true, "primary_lineage": true}
]
}
]
}
(Shortened: the real output has every link, CVSS score and linked CWE, and each
result also carries abstraction, status, published and url.) A link that
cannot be placed has "in_view": false and its explanation in note.
Limitations¶
- Sources disagree, and no winner is chosen. All links are shown with their sources so that you can judge them.
- Coverage depends on the CNA. Many CVEs, older ones especially, have no CWE in their CNA record, and some have no CWE from any source.
- Observed examples are citations, not official links. CWE authors choose them to illustrate a weakness, so they are often more specific or more general than the official link (Heartbleed under CWE-126, CWE-130 and CWE-240).
- One view at a time. Links are placed in View 1000, or in the view chosen with
--view(cwex CVE-2021-44228 --view 1003shows where the NVD's own view puts them). Links to groupings, views, deprecated entries and NVD placeholders, and to CWEs outside the chosen view, are listed but cannot be drawn. - From a CWE to its CVEs, only through the NVD.
--cvescounts the CVEs whose NVD record lists the CWE; it does not merge sources the way a CVE lookup does. The catalog's observed examples are the only other CWE → CVE information, and they are not exhaustive.
Where it lives in the code¶
| File | Role |
|---|---|
cwe_explorer/data.py |
Parses the CWE XML, including the observed index (CVE → citing CWEs) stored in catalog.json |
cwe_explorer/cve.py |
parse_cve_id(), lookup_cve() (cache, CVE.org, NVD, fallback), merge rules (CVE.add(), _from_cve_org(), _from_nvd()), exploitation signals (_signal(), CVE.add_kev(), fetch_epss()), cves_for_cwe() and the NVD rate limit (_nvd_wait()), mapping_note(), linked_cwes(), cve_payload() |
cwe_explorer/render_term.py |
CVE card (cve_fields()), merged tree (build_rows() with several focuses), print_cve() |
cwe_explorer/tui.py |
The CVE row, / lookups of other CVEs, history that remembers the loaded CVE |
cwe_explorer/render_html.py |
Puts the merged record and the observed index into the page |
cwe_explorer/web/viewer.js |
CVE node, panel and live lookups (setCve(), fetchCve(), lookupCve()) |
tests/test_cwe_explorer.py |
CVETest: merging, cache, offline fallback, merged tree and CLI, with the APIs simulated |