markdown-it: Renderizado en tiempo cuadrático de una sola definición de referencia de enlace multilínea con opciones predeterminadas (regresión desde 14.0)
Resumen
Una definición de referencia de enlace cuya etiqueta o título nunca se cierra tiene una complejidad O(n²) en markdown-it 15.0.2 con las opciones predeterminadas. Las líneas de continuación se obtienen de forma diferida con str += lineContent en src/rules_block/reference.ts, y las llamadas posteriores a charCodeAt y slice aplanan el cons-string de V8, copiando todo lo recopilado hasta el momento; esto se ejecuta antes de que la regla decida que la definición es inválida. Es una regresión introducida por el PR #1004 en 14.0.0: 13.0.2 procesaba una entrada de 313 KB en 125 ms, mientras que cualquier versión posterior tarda unos 11 segundos con la misma entrada. En los tamaños indicados abajo, 200 KB tarda 3.6 segundos, 400 KB tarda 18.1 segundos y 800 KB tarda 74.0 segundos, lo que equivale aproximadamente a 4–5× por duplicación. No se requiere ninguna opción especial para provocarlo.
Detalles
src/rules_block/reference.ts añade líneas de continuación con str += lineContent en las líneas 74, 83, 100, 133 y 150, y luego continúa indexando la cadena acumulada mediante str.charCodeAt(pos) en los bucles de etiqueta y espacios en blanco, y str.slice(...) en parseLinkTitle. V8 representa += como un cons-string, y la siguiente llamada a charCodeAt o slice lo aplana, copiando todo lo recopilado hasta el momento, por lo que una definición que abarca n líneas cuesta O(n²). El análisis en aislamiento confirma que esta regla es la causante: el análisis exclusivo de bloques con la funcionalidad inline desactivada sigue siendo cuadrático, md.disable('reference') hace que la misma entrada sea lineal, y el mismo texto sin el [ inicial es lineal. La corrección consiste en rastrear las posiciones en state.src en lugar de construir str, o recopilar fragmentos de línea en un array y analizarlos. Como mínimo, detener el escaneo de la etiqueta tras 999 caracteres (CommonMark §6.3) antes de solicitar más líneas, y limitar la continuación del título. Se debe añadir una prueba patológica con un título y una etiqueta no cerrados de 100k líneas. El PR abierto #1215 modifica el mismo archivo, pero su comprobación de etiquetas se ejecuta después del bucle de etiquetas, por lo que no limita ninguna de las dos variantes y la solución puede incorporarse junto con dicho PR.
La salida observada en la PoC fue: unclosed title n=100000 200009 B 3585.5 ms unclosed title n=200000 400009 B 18107.1 ms (5.05x) unclosed title n=400000 800009 B 73954.4 ms (4.08x)
Comparación de regresión a 313 KB: 13.0.2 → 125 ms, 14.1.0 → 11.4 s, 15.0.0 → 10.6 s, 15.0.2 → 10.7 s.
Prueba de concepto
Cómo ejecutar la PoC:
- mkdir -p markdown-it-poc && cd markdown-it-poc
- npm init -y
- npm i markdown-it@15.0.2
- node poc_markdown_it.js
La PoC es:
// poc_markdown_it.js
// Requires: npm install markdown-it@15.0.2
const MarkdownIt = require('markdown-it');
const md = new MarkdownIt();
function bench(label, src) {
const t0 = process.hrtime.bigint();
md.render(src);
const t1 = process.hrtime.bigint();
console.log(
`${label.padEnd(30)} ${String(src.length).padStart(9)} B ${(Number(t1 - t0) / 1e6).toFixed(1).padStart(9)} ms`
);
}
// Unclosed title: one reference definition spread over many lines
for (const n of [100000, 200000, 400000]) {
bench(`unclosed title n=${n}`, '[a]: /u "' + 'x\n'.repeat(n));
}
// Unclosed label
for (const n of [5000, 10000, 20000]) {
bench(`unclosed label n=${n}`, '[' + 'a\n'.repeat(n));
}
Impacto
Se trata de una denegación de servicio por complejidad algorítmica (CWE-407). Cualquier servidor que procese Markdown de usuarios con markdown-it bajo las opciones predeterminadas se ve afectado, incluidos comentarios, issues, chats, wikis, previsualizaciones de documentación e interfaces de chat de LLM. Un cuerpo de solicitud de ~400 KB bloquea el bucle de eventos de Node.js durante unos 18 segundos y uno de ~800 KB durante unos 74 segundos. Pertenece a la misma clase y modelo de atacante que GHSA-253c, pero es accesible sin activar opciones adicionales, ya que la ruta afectada es una regla de bloque principal.
Qué hacer
Actualice markdown-it a la versión 14.3.3, 15.0.3 o posterior. Como mitigación temporal, puede desactivar la regla afectada utilizando md.disable('reference') si no se requieren definiciones de referencia de enlaces.
Software afectado
Haz clic en una tecnología para ver todas sus vulnerabilidades.
| Plataforma | Paquete | Versiones vulnerables | Corregida en |
|---|---|---|---|
| Paquete de npm | markdown-it | >= 14.1.0, < 14.3.3, >= 15.0.0, < 15.0.3 | 14.3.3, 15.0.3 |
Referencias
- https://github.com/markdown-it/markdown-it/security/advisories/GHSA-gfr2-rv44-r23c
- https://github.com/markdown-it/markdown-it/commit/48fbc465812c440020c663f040ab22deaf8f1eca
- https://github.com/markdown-it/markdown-it/commit/d99f48839d37af6fd8a2617516c43dab90755503
- https://github.com/markdown-it/markdown-it
- https://github.com/markdown-it/markdown-it/releases/tag/14.3.3
- https://github.com/markdown-it/markdown-it/releases/tag/15.0.3
- Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H- Debilidad (CWE)
CWE-400, CWE-407- Plataforma
- npm
- Paquete
markdown-it- Publicada
- 20.10.2026
- Actualizado
- 20.10.2026 23:23
Fuentes y licencias · Editado o traducido por DockRay; se mantienen los derechos y las licencias de las fuentes.
OSV / GitHub Advisory Database - GHSA-gfr2-rv44-r23c - CC BY 4.0
Historial de vulnerabilidades: markdown-it
Todas las vulnerabilidades publicadas de este paquete (Paquete de npm), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.
- Entradas en total
- 8
- Primera → última
- 01.2022 10.2026
- Sin corrección
- 0
- Explotada en ataques
- 0
Distribución por gravedad
- Alta 3
- Media 4
- Baja 1
Divulgaciones por año
- 2022: 2
- 2023: 0
- 2024: 0
- 2025: 0
- 2026: 6
-
markdown-it: Renderizado en tiempo cuadrático de una sola definición de referencia de enlace multilínea con opciones predeterminadas (regresión desde 14.0) esta entrada
Identifica las vulnerabilidades de tu aplicación
Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.