cwe-explorer 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cwe-explorer might be problematic. Click here for more details.

@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ # El sdist incluye los tests; su catálogo de ejemplo también hace falta para ejecutarlos.
2
+ recursive-include tests *.xml
3
+ # El test que comprueba que docs/pypi.md está al día necesita el README y el generador.
4
+ include README.md tools/pypi_readme.py
@@ -0,0 +1,311 @@
1
+ Metadata-Version: 2.4
2
+ Name: cwe-explorer
3
+ Version: 0.1.0
4
+ Summary: Explora la jerarquía CWE de MITRE (vista 1000): árbol en la terminal, explorador interactivo, grafo web y consulta de ascendientes y descendientes
5
+ Author-email: Emi <edm@unex.es>
6
+ License-Expression: MIT
7
+ Keywords: cwe,mitre,weakness,vulnerability,security,hierarchy,cli,tui,graph
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Environment :: Console :: Curses
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Natural Language :: Spanish
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Security
25
+ Classifier: Topic :: Utilities
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Dynamic: license-file
30
+
31
+ <!-- Generado por tools/pypi_readme.py a partir de README.md: no editar a mano. -->
32
+
33
+ # CWE Explorer
34
+
35
+ Explora la jerarquía de debilidades de la **vista 1000 de MITRE** (*Complete
36
+ Weakness Hierarchy*, antes *Research Concepts*) a partir de un identificador CWE.
37
+ Muestra el linaje hasta el pilar del que desciende y los descendientes que cuelgan
38
+ de ella, y lista sus ascendientes o descendientes, directos o por niveles, también
39
+ en JSON.
40
+
41
+ Al seleccionar cualquier nodo se muestra su **ID**, su **descripción**, su **tipo**
42
+ (Pillar, Class, Base, Variant o Compound), su estructura y estado, y su **linaje**
43
+ completo: el camino principal hasta el pilar y cualquier otro camino alternativo
44
+ cuando la debilidad tiene varios padres.
45
+
46
+ Funciona en dos sitios:
47
+
48
+ - **Terminal**: árbol a color, explorador interactivo (`--tui`) que se maneja con el
49
+ ratón o el teclado, y listados de ascendientes y descendientes (`--ancestors`,
50
+ `--descendants`).
51
+ - **Navegador**: una página HTML/CSS autocontenida (`--web`) con el grafo
52
+ interactivo.
53
+
54
+ ## Instalación
55
+
56
+ Requiere Python 3.9 o superior y no tiene dependencias externas.
57
+
58
+ ```sh
59
+ pip install cwe-explorer # o: pipx install cwe-explorer / uv tool install cwe-explorer
60
+ cwex 79
61
+ ```
62
+
63
+ Se instalan dos comandos equivalentes: `cwex`, el corto, y `cwe-explorer`. En
64
+ Windows, el explorador interactivo (`--tui`) necesita además `pip install
65
+ windows-curses`; el resto funciona sin nada más.
66
+
67
+ También se puede ejecutar como módulo, ya sea instalado o desde una copia del código:
68
+
69
+ ```sh
70
+ python3 -m cwe_explorer 79
71
+ ```
72
+
73
+ La primera ejecución descarga el catálogo oficial (`cwec_latest.xml.zip`, unos 2 MB)
74
+ desde cwe.mitre.org y lo guarda en caché.
75
+
76
+ ## Uso
77
+
78
+ ```sh
79
+ cwex 79 # árbol en la terminal (linaje + 2 niveles de descendientes)
80
+ cwex CWE-79 --tui # explorador interactivo en la terminal
81
+ cwex 79 --web # grafo interactivo en el navegador
82
+ cwex 664 --depth all # todos los descendientes (también --depth 0, 1, 2...)
83
+ cwex --search "race" # buscar debilidades por nombre
84
+ cwex 79 --ancestors # modo 1: ascendientes directos (padres)
85
+ cwex 79 --descendants # modo 2: descendientes directos (hijos)
86
+ cwex 79 --web -o xss.html --no-open # guardar el HTML sin abrirlo
87
+ cwex --update # descargar de nuevo el catálogo más reciente
88
+ ```
89
+
90
+ El identificador se acepta como `79`, `CWE-79` o `cwe79`. Si el ID corresponde a una
91
+ agrupación (las antiguas *Categories*), a una vista o a una entrada obsoleta, el
92
+ programa explica por qué no aparece en la vista 1000.
93
+
94
+ ### Terminal
95
+
96
+ ```
97
+ Ba CWE-79 Improper Neutralization of Input During Web Page Generation
98
+ Tipo Base: debilidad todavía abstracta, pero con el detalle suficiente
99
+ para deducir métodos concretos de detección y prevención.
100
+ Estructura Simple
101
+ Estado Stable
102
+ Descripción The product does not neutralize or incorrectly neutralizes ...
103
+ Linaje CWE-707 › CWE-74 › CWE-79
104
+ Hijos CWE-80, CWE-81, CWE-83, CWE-84, CWE-85, CWE-86, CWE-87
105
+ ...
106
+
107
+ Jerarquía en la vista 1000 (CWE 5.0, profundidad 2)
108
+ Pi CWE-707 Improper Neutralization
109
+ └─ Cl CWE-74 Improper Neutralization of Special Elements in Output Used by ...
110
+ └─ Ba CWE-79 Improper Neutralization of Input During Web Page Generation ◀
111
+ ├─ Va CWE-80 Improper Neutralization of Script-Related HTML Tags in ...
112
+ ├─ Va CWE-83 Improper Neutralization of Script in Attributes in a Web Page
113
+ │ └─ Va CWE-82 Improper Neutralization of Script in Attributes of IMG ...
114
+ ...
115
+ ```
116
+
117
+ Cuando una debilidad tiene varios linajes, todos se dibujan en el árbol y el
118
+ subárbol de descendientes se muestra una sola vez.
119
+
120
+ Explorador interactivo (`--tui`):
121
+
122
+ | Acción | Ratón | Teclado |
123
+ | --- | --- | --- |
124
+ | Ver la ficha de un nodo | clic | `↑` `↓` / `j` `k` |
125
+ | Centrar la jerarquía en ese nodo | doble clic | `Enter` / `→` |
126
+ | Volver al nodo anterior | | `⌫` / `b` / `←` |
127
+ | Más o menos niveles de descendientes | | `+` / `-` |
128
+ | Ir a otra CWE o buscar por nombre | | `/` |
129
+ | Desplazar la ficha | rueda sobre la ficha | `J` / `K` |
130
+ | Abrir la ficha en cwe.mitre.org | | `o` |
131
+ | Abrir el nodo en el grafo web | | `w` |
132
+ | Salir | | `q` |
133
+
134
+ ### Navegador
135
+
136
+ - **Clic** en un nodo: muestra su ficha en el panel lateral y resalta su linaje
137
+ hasta el pilar (trazo grueso: linaje principal).
138
+ - **Doble clic**, o el botón *Centrar la jerarquía*: reconstruye el grafo alrededor
139
+ de ese nodo.
140
+ - **`+N` / `−`** en un nodo: despliega u oculta sus hijos. En los ancestros sirve
141
+ para ver las debilidades hermanas.
142
+ - Buscador por ID o por nombre, selector de niveles de descendientes, zoom con la
143
+ rueda y arrastre para desplazarse.
144
+ - **Pantalla completa** (botón o tecla `F`): el grafo ocupa todo el monitor y la
145
+ ficha aparece como un panel flotante al hacer clic en un nodo (en el móvil, desde
146
+ abajo). Se cierra con `×` o `Esc`; `Esc` también sale de la pantalla completa.
147
+ - Aristas continuas para el padre primario y discontinuas para los padres
148
+ secundarios.
149
+ - La página incluye toda la vista 1000, así que se puede navegar a cualquier otra
150
+ CWE sin volver a generarla. El enlace `#cwe-89` abre directamente esa debilidad.
151
+ Respeta el modo oscuro del sistema y se adapta al móvil.
152
+
153
+ Por defecto el HTML se guarda en `~/.cache/cwe-explorer/html/`. Usa `-o` para
154
+ elegir otra ruta.
155
+
156
+ ### Ascendientes y descendientes (modos 1 y 2)
157
+
158
+ Dada una CWE, lista sus ascendientes (modo 1) o sus descendientes (modo 2) en la
159
+ vista 1000. Por defecto sólo los directos, es decir, sus padres o sus hijos:
160
+
161
+ ```sh
162
+ cwex 79 --ancestors # padres de CWE-79
163
+ cwex 79 --descendants # hijos de CWE-79
164
+ cwex 425 --ancestors --depth all # todos los ascendientes, hasta los pilares
165
+ cwex 664 --descendants --depth 2 # hijos y nietos
166
+ cwex 664 --descendants -d all --json > cwe-664.json
167
+ ```
168
+
169
+ `--ascendientes` y `--descendientes` son alias de las mismas opciones. Con
170
+ `--depth N` se recorren N niveles y con `--depth all`, todos. Cuando hay más de un
171
+ nivel, el resultado se agrupa por distancia:
172
+
173
+ ```
174
+ Ba CWE-425 Direct Request
175
+ Todos los ascendientes en la vista 1000 (CWE 5.0): 11 (3 Pillar, 6 Class, 2 Base)
176
+
177
+ Nivel 1 (padres)
178
+ Cl CWE-862 Missing Authorization (padre primario)
179
+ Ba CWE-288 Authentication Bypass Using an Alternate Path or Channel (padre secundario)
180
+ Cl CWE-424 Improper Protection of Alternate Path (padre secundario)
181
+
182
+ Nivel 2
183
+ Cl CWE-285 Improper Authorization (vía CWE-862 · linaje principal)
184
+ Ba CWE-306 Missing Authentication for Critical Functionality (vía CWE-288)
185
+ Cl CWE-638 Not Using Complete Mediation (vía CWE-424)
186
+ Pi CWE-693 Protection Mechanism Failure (vía CWE-424)
187
+ ...
188
+ ```
189
+
190
+ Las notas entre paréntesis explican el parentesco:
191
+
192
+ - **Padres**: `padre primario` o `padre secundario`, según el atributo
193
+ `Ordinal="Primary"` del catálogo.
194
+ - **Hijos**: `vínculo secundario` cuando la CWE consultada no es su padre primario,
195
+ junto con cuál es ese padre primario.
196
+ - **Niveles siguientes**: `vía CWE-…`, que son los nodos del nivel anterior por los
197
+ que se llega. Además, `linaje principal` en los ascendientes que están en el
198
+ camino de padres primarios, y `fuera de su linaje principal` en los descendientes
199
+ que sólo llegan a la CWE consultada a través de algún padre secundario.
200
+
201
+ Con `--json` la salida incluye la CWE consultada (`cwe`, `name`, `abstraction`), el
202
+ modo (`mode`, `depth`), la versión del catálogo (`catalog`), `count` y una lista
203
+ `results` con un objeto por nodo:
204
+
205
+ | Campo | Significado |
206
+ | --- | --- |
207
+ | `id`, `name`, `abstraction`, `status`, `url` | Datos de la debilidad |
208
+ | `distance` | 1 = padre o hijo, 2 = abuelo o nieto… Si hay varios caminos, se usa el más corto |
209
+ | `via` | IDs del nivel anterior por los que se llega (en distancia 1, la propia CWE) |
210
+ | `primary_parent` | Sólo en distancia 1: si el `ChildOf` entre ambas es el primario. `null` en el resto |
211
+ | `primary_lineage` | En ascendientes: si está en el linaje principal de la CWE. En descendientes: si la CWE está en el linaje principal del nodo |
212
+
213
+ El campo `note` explica un resultado vacío o un linaje incompleto, y vale `null` en
214
+ el resto de casos. Desde Python se obtiene lo mismo con
215
+ `Hierarchy.relatives(cwe_id, "ancestors" | "descendants", depth=1)`. Con
216
+ `depth=None` se recorren todos los niveles:
217
+
218
+ ```python
219
+ from cwe_explorer.data import load_catalog
220
+ from cwe_explorer.hierarchy import Hierarchy
221
+
222
+ h = Hierarchy(load_catalog())
223
+ [r.id for r in h.relatives(79, "ancestors")] # [74]
224
+ [(r.id, r.distance) for r in h.relatives(83, "ancestors", depth=None)]
225
+ # [(79, 1), (74, 2), (707, 3)]
226
+ ```
227
+
228
+ ## Limitaciones de los ascendientes y descendientes
229
+
230
+ Los dos modos se pueden calcular con los datos que publica MITRE, pero la vista 1000
231
+ no es un árbol simple, y eso condiciona cómo leer el resultado. Las cifras son del
232
+ catálogo CWE 5.0 (2026-10-28).
233
+
234
+ 1. **Es un grafo con varios padres, no un árbol.**
235
+ - 152 de las 957 debilidades tienen más de un padre directo, hasta 3 (CWE-396,
236
+ CWE-397, CWE-415, CWE-425 y CWE-1299). De los 1106 vínculos `ChildOf`, 160 son
237
+ secundarios.
238
+ - Por eso el modo 1 puede devolver varios padres, y la lista completa de
239
+ ascendientes puede abarcar varias ramas: 279 debilidades tienen más de un
240
+ linaje y 235 llegan a más de un pilar (CWE-425 llega a tres).
241
+ - MITRE sólo indica cuál es el padre primario. Entre los demás padres no hay
242
+ ningún orden de importancia.
243
+ 2. **"Directo" significa un enlace `ChildOf`, no el nivel de abstracción
244
+ contiguo.** Los enlaces no siempre bajan un escalón Pillar → Class → Base →
245
+ Variant:
246
+ - 83 Base y 3 Variant (CWE-580, CWE-581 y CWE-1077) cuelgan directamente de un
247
+ Pillar, y 85 Variant cuelgan de una Class.
248
+ - 177 enlaces unen debilidades del mismo tipo: 88 Base–Base, 62 Class–Class y
249
+ 27 Variant–Variant.
250
+ - Hay 2 Class que cuelgan de una Base: CWE-114 de CWE-73 y CWE-240 de CWE-237.
251
+
252
+ La herramienta devuelve lo que publica el catálogo; no deduce padres a partir
253
+ del tipo.
254
+ 3. **Un mismo ascendiente puede estar a varias distancias.**
255
+ - En 25 debilidades hay caminos de distinta longitud hacia un mismo ascendiente.
256
+ Por ejemplo, CWE-102 llega a CWE-707 en 3 niveles y también en 4.
257
+ - En 4 casos, un padre directo es además un ascendiente más lejano (CWE-240,
258
+ CWE-415, CWE-425 y CWE-1262).
259
+
260
+ Cada nodo aparece una sola vez, a la distancia más corta, y `via` dice por dónde
261
+ se llega. `--depth N` también cuenta los niveles por el camino más corto. Los
262
+ demás caminos se ven con `cwex <id>`, que dibuja todos los linajes, o en el
263
+ grafo web.
264
+ 4. **Sólo la vista 1000 y sólo `ChildOf`.**
265
+ - Otras vistas tienen su propia jerarquía. Algunas usan `ChildOf` con otro
266
+ `View_ID` (por ejemplo la 1003, la que usa NVD, o la 1305). Otras se organizan
267
+ con agrupaciones y `Has_Member` (por ejemplo la 699). Este programa no carga
268
+ ninguna de ellas.
269
+ - Las 431 agrupaciones (antes *Categories*) y las 62 vistas no son debilidades.
270
+ Si se consulta una, el programa explica qué es y termina con error en lugar de
271
+ listar sus miembros.
272
+ - El XML no publica relaciones `ParentOf`: los hijos se obtienen invirtiendo
273
+ los `ChildOf`.
274
+ 5. **Las demás relaciones no son parentesco.** `CanPrecede` (148), `PeerOf` (103),
275
+ `CanAlsoBe` (28), `Requires` (13), `StartsWith` (3) y `Overlaps` (3) no son
276
+ jerárquicas y no se recorren. Aparecen en la ficha de cada CWE (`cwex <id>`).
277
+ 6. **Los Compound no tienen hijos.** Las 7 debilidades Compound (por ejemplo
278
+ CWE-352) son hojas de la vista 1000, porque sus componentes se enlazan con
279
+ `Requires` o `StartsWith`. El modo 2 lo explica y nombra esos componentes, pero
280
+ no los cuenta como descendientes.
281
+ 7. **Hay resultados vacíos esperables.** Los 10 Pillar no tienen ascendientes, y 692
282
+ debilidades no tienen descendientes: 390 Base, 274 Variant, 21 Class y las 7
283
+ Compound. En esos casos el programa añade una nota (`note` en JSON) y termina con
284
+ código 0.
285
+ 8. **Entradas obsoletas.** Las 27 entradas obsoletas que no forman parte de la vista
286
+ no se pueden consultar. La excepción es CWE-377: está obsoleta, pero CWE-378 y
287
+ CWE-379 siguen colgando de ella. Por eso sus ascendientes terminan en CWE-377 con
288
+ un aviso de linaje incompleto.
289
+ 9. **Los resultados dependen de la versión del catálogo.** MITRE cambia relaciones de
290
+ una versión a otra. El programa usa la copia en caché hasta que se ejecuta
291
+ `cwex --update`.
292
+
293
+ ## Datos
294
+
295
+ - Fuente: <https://cwe.mitre.org/data/xml/cwec_latest.xml.zip>. Solo se usan las
296
+ relaciones `ChildOf` con `View_ID="1000"`, y el padre marcado como
297
+ `Ordinal="Primary"` define el linaje principal.
298
+ - Caché: `~/.cache/cwe-explorer/`, o `$XDG_CACHE_HOME/cwe-explorer`, o la ruta
299
+ de `CWEX_CACHE`.
300
+ - Para trabajar sin conexión con un XML concreto: `cwex 79 --xml cwec_v5.0.xml`
301
+ (también acepta el `.zip`).
302
+ - El parser no depende del namespace del esquema (`cwe-7`, `cwe-8`...), así que
303
+ sirve para catálogos antiguos y nuevos.
304
+
305
+ > En CWE 5.0, CWE-378 y CWE-379 siguen colgando de CWE-377, que está marcada como
306
+ > obsoleta. La herramienta conserva ese enlace tal como lo publica MITRE, marca el
307
+ > nodo como obsoleto y avisa de que ese linaje no llega a un pilar.
308
+
309
+ ## Licencia
310
+
311
+ MIT © 2026 Emi
@@ -0,0 +1,337 @@
1
+ <p align="center">
2
+ <img src="assets/logo.jpeg" alt="CWE Explorer" width="220">
3
+ </p>
4
+
5
+ # CWE Explorer
6
+
7
+ Explora la jerarquía de debilidades de la **vista 1000 de MITRE** (*Complete
8
+ Weakness Hierarchy*, antes *Research Concepts*) a partir de un identificador CWE.
9
+ Muestra el linaje hasta el pilar del que desciende y los descendientes que cuelgan
10
+ de ella, y lista sus ascendientes o descendientes, directos o por niveles, también
11
+ en JSON.
12
+
13
+ Al seleccionar cualquier nodo se muestra su **ID**, su **descripción**, su **tipo**
14
+ (Pillar, Class, Base, Variant o Compound), su estructura y estado, y su **linaje**
15
+ completo: el camino principal hasta el pilar y cualquier otro camino alternativo
16
+ cuando la debilidad tiene varios padres.
17
+
18
+ Funciona en dos sitios:
19
+
20
+ - **Terminal**: árbol a color, explorador interactivo (`--tui`) que se maneja con el
21
+ ratón o el teclado, y listados de ascendientes y descendientes (`--ancestors`,
22
+ `--descendants`).
23
+ - **Navegador**: una página HTML/CSS autocontenida (`--web`) con el grafo
24
+ interactivo.
25
+
26
+ ![Grafo de CWE-79 en el navegador, con CWE-83 seleccionada](docs/web-viewer.png)
27
+
28
+ ## Instalación
29
+
30
+ Requiere Python 3.9 o superior y no tiene dependencias externas.
31
+
32
+ ```sh
33
+ pip install cwe-explorer # o: pipx install cwe-explorer / uv tool install cwe-explorer
34
+ cwex 79
35
+ ```
36
+
37
+ Se instalan dos comandos equivalentes: `cwex`, el corto, y `cwe-explorer`. En
38
+ Windows, el explorador interactivo (`--tui`) necesita además `pip install
39
+ windows-curses`; el resto funciona sin nada más.
40
+
41
+ También se puede ejecutar como módulo, ya sea instalado o desde una copia del código:
42
+
43
+ ```sh
44
+ python3 -m cwe_explorer 79
45
+ ```
46
+
47
+ La primera ejecución descarga el catálogo oficial (`cwec_latest.xml.zip`, unos 2 MB)
48
+ desde cwe.mitre.org y lo guarda en caché.
49
+
50
+ ## Uso
51
+
52
+ ```sh
53
+ cwex 79 # árbol en la terminal (linaje + 2 niveles de descendientes)
54
+ cwex CWE-79 --tui # explorador interactivo en la terminal
55
+ cwex 79 --web # grafo interactivo en el navegador
56
+ cwex 664 --depth all # todos los descendientes (también --depth 0, 1, 2...)
57
+ cwex --search "race" # buscar debilidades por nombre
58
+ cwex 79 --ancestors # modo 1: ascendientes directos (padres)
59
+ cwex 79 --descendants # modo 2: descendientes directos (hijos)
60
+ cwex 79 --web -o xss.html --no-open # guardar el HTML sin abrirlo
61
+ cwex --update # descargar de nuevo el catálogo más reciente
62
+ ```
63
+
64
+ El identificador se acepta como `79`, `CWE-79` o `cwe79`. Si el ID corresponde a una
65
+ agrupación (las antiguas *Categories*), a una vista o a una entrada obsoleta, el
66
+ programa explica por qué no aparece en la vista 1000.
67
+
68
+ ### Terminal
69
+
70
+ ```
71
+ Ba CWE-79 Improper Neutralization of Input During Web Page Generation
72
+ Tipo Base: debilidad todavía abstracta, pero con el detalle suficiente
73
+ para deducir métodos concretos de detección y prevención.
74
+ Estructura Simple
75
+ Estado Stable
76
+ Descripción The product does not neutralize or incorrectly neutralizes ...
77
+ Linaje CWE-707 › CWE-74 › CWE-79
78
+ Hijos CWE-80, CWE-81, CWE-83, CWE-84, CWE-85, CWE-86, CWE-87
79
+ ...
80
+
81
+ Jerarquía en la vista 1000 (CWE 5.0, profundidad 2)
82
+ Pi CWE-707 Improper Neutralization
83
+ └─ Cl CWE-74 Improper Neutralization of Special Elements in Output Used by ...
84
+ └─ Ba CWE-79 Improper Neutralization of Input During Web Page Generation ◀
85
+ ├─ Va CWE-80 Improper Neutralization of Script-Related HTML Tags in ...
86
+ ├─ Va CWE-83 Improper Neutralization of Script in Attributes in a Web Page
87
+ │ └─ Va CWE-82 Improper Neutralization of Script in Attributes of IMG ...
88
+ ...
89
+ ```
90
+
91
+ Cuando una debilidad tiene varios linajes, todos se dibujan en el árbol y el
92
+ subárbol de descendientes se muestra una sola vez.
93
+
94
+ Explorador interactivo (`--tui`):
95
+
96
+ | Acción | Ratón | Teclado |
97
+ | --- | --- | --- |
98
+ | Ver la ficha de un nodo | clic | `↑` `↓` / `j` `k` |
99
+ | Centrar la jerarquía en ese nodo | doble clic | `Enter` / `→` |
100
+ | Volver al nodo anterior | | `⌫` / `b` / `←` |
101
+ | Más o menos niveles de descendientes | | `+` / `-` |
102
+ | Ir a otra CWE o buscar por nombre | | `/` |
103
+ | Desplazar la ficha | rueda sobre la ficha | `J` / `K` |
104
+ | Abrir la ficha en cwe.mitre.org | | `o` |
105
+ | Abrir el nodo en el grafo web | | `w` |
106
+ | Salir | | `q` |
107
+
108
+ ### Navegador
109
+
110
+ - **Clic** en un nodo: muestra su ficha en el panel lateral y resalta su linaje
111
+ hasta el pilar (trazo grueso: linaje principal).
112
+ - **Doble clic**, o el botón *Centrar la jerarquía*: reconstruye el grafo alrededor
113
+ de ese nodo.
114
+ - **`+N` / `−`** en un nodo: despliega u oculta sus hijos. En los ancestros sirve
115
+ para ver las debilidades hermanas.
116
+ - Buscador por ID o por nombre, selector de niveles de descendientes, zoom con la
117
+ rueda y arrastre para desplazarse.
118
+ - **Pantalla completa** (botón o tecla `F`): el grafo ocupa todo el monitor y la
119
+ ficha aparece como un panel flotante al hacer clic en un nodo (en el móvil, desde
120
+ abajo). Se cierra con `×` o `Esc`; `Esc` también sale de la pantalla completa.
121
+ - Aristas continuas para el padre primario y discontinuas para los padres
122
+ secundarios.
123
+ - La página incluye toda la vista 1000, así que se puede navegar a cualquier otra
124
+ CWE sin volver a generarla. El enlace `#cwe-89` abre directamente esa debilidad.
125
+ Respeta el modo oscuro del sistema y se adapta al móvil.
126
+
127
+ Por defecto el HTML se guarda en `~/.cache/cwe-explorer/html/`. Usa `-o` para
128
+ elegir otra ruta.
129
+
130
+ ### Ascendientes y descendientes (modos 1 y 2)
131
+
132
+ Dada una CWE, lista sus ascendientes (modo 1) o sus descendientes (modo 2) en la
133
+ vista 1000. Por defecto sólo los directos, es decir, sus padres o sus hijos:
134
+
135
+ ```sh
136
+ cwex 79 --ancestors # padres de CWE-79
137
+ cwex 79 --descendants # hijos de CWE-79
138
+ cwex 425 --ancestors --depth all # todos los ascendientes, hasta los pilares
139
+ cwex 664 --descendants --depth 2 # hijos y nietos
140
+ cwex 664 --descendants -d all --json > cwe-664.json
141
+ ```
142
+
143
+ `--ascendientes` y `--descendientes` son alias de las mismas opciones. Con
144
+ `--depth N` se recorren N niveles y con `--depth all`, todos. Cuando hay más de un
145
+ nivel, el resultado se agrupa por distancia:
146
+
147
+ ```
148
+ Ba CWE-425 Direct Request
149
+ Todos los ascendientes en la vista 1000 (CWE 5.0): 11 (3 Pillar, 6 Class, 2 Base)
150
+
151
+ Nivel 1 (padres)
152
+ Cl CWE-862 Missing Authorization (padre primario)
153
+ Ba CWE-288 Authentication Bypass Using an Alternate Path or Channel (padre secundario)
154
+ Cl CWE-424 Improper Protection of Alternate Path (padre secundario)
155
+
156
+ Nivel 2
157
+ Cl CWE-285 Improper Authorization (vía CWE-862 · linaje principal)
158
+ Ba CWE-306 Missing Authentication for Critical Functionality (vía CWE-288)
159
+ Cl CWE-638 Not Using Complete Mediation (vía CWE-424)
160
+ Pi CWE-693 Protection Mechanism Failure (vía CWE-424)
161
+ ...
162
+ ```
163
+
164
+ Las notas entre paréntesis explican el parentesco:
165
+
166
+ - **Padres**: `padre primario` o `padre secundario`, según el atributo
167
+ `Ordinal="Primary"` del catálogo.
168
+ - **Hijos**: `vínculo secundario` cuando la CWE consultada no es su padre primario,
169
+ junto con cuál es ese padre primario.
170
+ - **Niveles siguientes**: `vía CWE-…`, que son los nodos del nivel anterior por los
171
+ que se llega. Además, `linaje principal` en los ascendientes que están en el
172
+ camino de padres primarios, y `fuera de su linaje principal` en los descendientes
173
+ que sólo llegan a la CWE consultada a través de algún padre secundario.
174
+
175
+ Con `--json` la salida incluye la CWE consultada (`cwe`, `name`, `abstraction`), el
176
+ modo (`mode`, `depth`), la versión del catálogo (`catalog`), `count` y una lista
177
+ `results` con un objeto por nodo:
178
+
179
+ | Campo | Significado |
180
+ | --- | --- |
181
+ | `id`, `name`, `abstraction`, `status`, `url` | Datos de la debilidad |
182
+ | `distance` | 1 = padre o hijo, 2 = abuelo o nieto… Si hay varios caminos, se usa el más corto |
183
+ | `via` | IDs del nivel anterior por los que se llega (en distancia 1, la propia CWE) |
184
+ | `primary_parent` | Sólo en distancia 1: si el `ChildOf` entre ambas es el primario. `null` en el resto |
185
+ | `primary_lineage` | En ascendientes: si está en el linaje principal de la CWE. En descendientes: si la CWE está en el linaje principal del nodo |
186
+
187
+ El campo `note` explica un resultado vacío o un linaje incompleto, y vale `null` en
188
+ el resto de casos. Desde Python se obtiene lo mismo con
189
+ `Hierarchy.relatives(cwe_id, "ancestors" | "descendants", depth=1)`. Con
190
+ `depth=None` se recorren todos los niveles:
191
+
192
+ ```python
193
+ from cwe_explorer.data import load_catalog
194
+ from cwe_explorer.hierarchy import Hierarchy
195
+
196
+ h = Hierarchy(load_catalog())
197
+ [r.id for r in h.relatives(79, "ancestors")] # [74]
198
+ [(r.id, r.distance) for r in h.relatives(83, "ancestors", depth=None)]
199
+ # [(79, 1), (74, 2), (707, 3)]
200
+ ```
201
+
202
+ ## Limitaciones de los ascendientes y descendientes
203
+
204
+ Los dos modos se pueden calcular con los datos que publica MITRE, pero la vista 1000
205
+ no es un árbol simple, y eso condiciona cómo leer el resultado. Las cifras son del
206
+ catálogo CWE 5.0 (2026-10-28).
207
+
208
+ 1. **Es un grafo con varios padres, no un árbol.**
209
+ - 152 de las 957 debilidades tienen más de un padre directo, hasta 3 (CWE-396,
210
+ CWE-397, CWE-415, CWE-425 y CWE-1299). De los 1106 vínculos `ChildOf`, 160 son
211
+ secundarios.
212
+ - Por eso el modo 1 puede devolver varios padres, y la lista completa de
213
+ ascendientes puede abarcar varias ramas: 279 debilidades tienen más de un
214
+ linaje y 235 llegan a más de un pilar (CWE-425 llega a tres).
215
+ - MITRE sólo indica cuál es el padre primario. Entre los demás padres no hay
216
+ ningún orden de importancia.
217
+ 2. **"Directo" significa un enlace `ChildOf`, no el nivel de abstracción
218
+ contiguo.** Los enlaces no siempre bajan un escalón Pillar → Class → Base →
219
+ Variant:
220
+ - 83 Base y 3 Variant (CWE-580, CWE-581 y CWE-1077) cuelgan directamente de un
221
+ Pillar, y 85 Variant cuelgan de una Class.
222
+ - 177 enlaces unen debilidades del mismo tipo: 88 Base–Base, 62 Class–Class y
223
+ 27 Variant–Variant.
224
+ - Hay 2 Class que cuelgan de una Base: CWE-114 de CWE-73 y CWE-240 de CWE-237.
225
+
226
+ La herramienta devuelve lo que publica el catálogo; no deduce padres a partir
227
+ del tipo.
228
+ 3. **Un mismo ascendiente puede estar a varias distancias.**
229
+ - En 25 debilidades hay caminos de distinta longitud hacia un mismo ascendiente.
230
+ Por ejemplo, CWE-102 llega a CWE-707 en 3 niveles y también en 4.
231
+ - En 4 casos, un padre directo es además un ascendiente más lejano (CWE-240,
232
+ CWE-415, CWE-425 y CWE-1262).
233
+
234
+ Cada nodo aparece una sola vez, a la distancia más corta, y `via` dice por dónde
235
+ se llega. `--depth N` también cuenta los niveles por el camino más corto. Los
236
+ demás caminos se ven con `cwex <id>`, que dibuja todos los linajes, o en el
237
+ grafo web.
238
+ 4. **Sólo la vista 1000 y sólo `ChildOf`.**
239
+ - Otras vistas tienen su propia jerarquía. Algunas usan `ChildOf` con otro
240
+ `View_ID` (por ejemplo la 1003, la que usa NVD, o la 1305). Otras se organizan
241
+ con agrupaciones y `Has_Member` (por ejemplo la 699). Este programa no carga
242
+ ninguna de ellas.
243
+ - Las 431 agrupaciones (antes *Categories*) y las 62 vistas no son debilidades.
244
+ Si se consulta una, el programa explica qué es y termina con error en lugar de
245
+ listar sus miembros.
246
+ - El XML no publica relaciones `ParentOf`: los hijos se obtienen invirtiendo
247
+ los `ChildOf`.
248
+ 5. **Las demás relaciones no son parentesco.** `CanPrecede` (148), `PeerOf` (103),
249
+ `CanAlsoBe` (28), `Requires` (13), `StartsWith` (3) y `Overlaps` (3) no son
250
+ jerárquicas y no se recorren. Aparecen en la ficha de cada CWE (`cwex <id>`).
251
+ 6. **Los Compound no tienen hijos.** Las 7 debilidades Compound (por ejemplo
252
+ CWE-352) son hojas de la vista 1000, porque sus componentes se enlazan con
253
+ `Requires` o `StartsWith`. El modo 2 lo explica y nombra esos componentes, pero
254
+ no los cuenta como descendientes.
255
+ 7. **Hay resultados vacíos esperables.** Los 10 Pillar no tienen ascendientes, y 692
256
+ debilidades no tienen descendientes: 390 Base, 274 Variant, 21 Class y las 7
257
+ Compound. En esos casos el programa añade una nota (`note` en JSON) y termina con
258
+ código 0.
259
+ 8. **Entradas obsoletas.** Las 27 entradas obsoletas que no forman parte de la vista
260
+ no se pueden consultar. La excepción es CWE-377: está obsoleta, pero CWE-378 y
261
+ CWE-379 siguen colgando de ella. Por eso sus ascendientes terminan en CWE-377 con
262
+ un aviso de linaje incompleto.
263
+ 9. **Los resultados dependen de la versión del catálogo.** MITRE cambia relaciones de
264
+ una versión a otra. El programa usa la copia en caché hasta que se ejecuta
265
+ `cwex --update`.
266
+
267
+ ## Datos
268
+
269
+ - Fuente: <https://cwe.mitre.org/data/xml/cwec_latest.xml.zip>. Solo se usan las
270
+ relaciones `ChildOf` con `View_ID="1000"`, y el padre marcado como
271
+ `Ordinal="Primary"` define el linaje principal.
272
+ - Caché: `~/.cache/cwe-explorer/`, o `$XDG_CACHE_HOME/cwe-explorer`, o la ruta
273
+ de `CWEX_CACHE`.
274
+ - Para trabajar sin conexión con un XML concreto: `cwex 79 --xml cwec_v5.0.xml`
275
+ (también acepta el `.zip`).
276
+ - El parser no depende del namespace del esquema (`cwe-7`, `cwe-8`...), así que
277
+ sirve para catálogos antiguos y nuevos.
278
+
279
+ > En CWE 5.0, CWE-378 y CWE-379 siguen colgando de CWE-377, que está marcada como
280
+ > obsoleta. La herramienta conserva ese enlace tal como lo publica MITRE, marca el
281
+ > nodo como obsoleto y avisa de que ese linaje no llega a un pilar.
282
+
283
+ ## Desarrollo
284
+
285
+ ```sh
286
+ python3 -m unittest discover -s tests
287
+ ```
288
+
289
+ ```
290
+ cwe_explorer/
291
+ data.py descarga, parseo del XML y caché JSON
292
+ hierarchy.py grafo de la vista 1000: ancestros, descendientes, linajes
293
+ render_term.py árbol y ficha para la terminal
294
+ tui.py explorador interactivo (curses)
295
+ render_html.py genera la página HTML autocontenida
296
+ web/ plantilla, estilos, JavaScript del grafo (layout por capas propio) y logo
297
+ cli.py argumentos de línea de comandos
298
+ ```
299
+
300
+ ### Publicar una versión
301
+
302
+ 1. Sube `__version__` en `cwe_explorer/__init__.py`; `pyproject.toml` la toma de ahí.
303
+ 2. Si has cambiado el README, regenera la descripción de PyPI (`docs/pypi.md`),
304
+ que es el README sin imágenes, sin enlaces relativos y sin esta sección,
305
+ porque el repositorio es privado:
306
+
307
+ ```sh
308
+ python3 tools/pypi_readme.py
309
+ ```
310
+
311
+ 3. Genera y comprueba el paquete:
312
+
313
+ ```sh
314
+ rm -rf dist && uv build
315
+ uvx twine check dist/*
316
+ ```
317
+
318
+ 4. Pruébalo primero en TestPyPI, con un token de <https://test.pypi.org> en la
319
+ variable `UV_PUBLISH_TOKEN`:
320
+
321
+ ```sh
322
+ uv publish --index testpypi
323
+ uvx --index-url https://test.pypi.org/simple/ --from cwe-explorer cwex 79 --ancestors
324
+ ```
325
+
326
+ 5. Publica en PyPI, con un token de <https://pypi.org> en `UV_PUBLISH_TOKEN`:
327
+
328
+ ```sh
329
+ uv publish
330
+ ```
331
+
332
+ Una versión publicada no se puede reemplazar: para corregir algo hay que subir un
333
+ número de versión nuevo.
334
+
335
+ ## Licencia
336
+
337
+ [MIT](LICENSE) © 2026 Emi
@@ -0,0 +1,3 @@
1
+ """Explorador de la jerarquía CWE (vista 1000 de MITRE) en la terminal y en el navegador."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())