Ir al contenido

Modelo de amenazas

Wu ejecuta código que no escribió. Eso es lo que hace un runtime de micro-frontends. Esta página dice con precisión qué significa eso para tu modelo de amenazas, sin suavizarlo.

┌─ Origen del navegador: https://shell.example.com ─────────────────────┐
│ │
│ Shell (RAÍZ CONFIABLE) │
│ Dueño del HTML, la config de wu, la autenticación y la CSP. │
│ Si esto se compromete, nada de lo de abajo importa. │
│ │ │
│ ├─ app module ──── corre en la ventana principal. Sin frontera. │
│ │ │
│ ├─ app strict ──── iframe del mismo origen. Globales separados. │
│ │ NO es frontera: se llega vía ownerDocument. │
│ │ │
│ └─ app eval ────── iframe del mismo origen, <script>s clásicos. │
│ Misma advertencia. │
│ │
│ Todo lo de arriba comparte: cookies, localStorage, IndexedDB, │
│ fetch con credenciales y el DOM. │
└───────────────────────────────────────────────────────────────────────┘
║ ← la única frontera real en un navegador
┌─ Origen distinto: https://sandbox.example.net ────────────────────────┐
│ Iframe de origen cruzado, sandbox="allow-scripts" │
│ y SIN allow-same-origin. │
│ Origen opaco. Sin cookies, sin almacenamiento, sin acceso al DOM. │
│ Solo se comunica por postMessage que tú validas. │
└───────────────────────────────────────────────────────────────────────┘

Hay una frontera de confianza en ese diagrama, y no la dibuja Wu. La dibuja el navegador, en el origen.

Estas son mitigaciones reales, implementadas.

Amenaza Mitigación
Fuga de CSS entre apps Cada app renderiza en un shadow root; styleMode controla qué entra
Colisiones de DOM entre apps Las apps consultan su propio shadow root; en strict/eval el document.querySelector y el document.body del iframe se redirigen allí
Suplantación de eventos eventBus.strictMode exige un token por cada emisión; los nombres de subsistemas internos (wu-core, wu-ai, wu-mcp-bridge, plugin) no se pueden reclamar sin el token interno
Tokens predecibles Se acuñan con crypto.randomUUID, con crypto.getRandomValues como respaldo; Math.random() solo si WebCrypto no está, y con un aviso
Contaminación de prototipos vía el store set() lanza error ante __proto__, constructor o prototype en cualquier segmento de la ruta; la guarda también corre sobre operaciones CRDT remotas
Manipulación del manifiesto Tamaño (100 KB), longitud de nombre y entry, path traversal, URLs javascript:/data:, <script, patrones on*=: todo rechazado. Un manifiesto que existe pero no pasa la validación lanza error; nunca se degrada a un valor por defecto permisivo
Path traversal en wu.use() Las URLs de componentes quedan confinadas bajo la URL base de la app; se rechazan el traversal (incluido el codificado en porcentaje), las URLs absolutas y los esquemas ejecutables
Handlers on* inyectados en HTML remoto Se eliminan del HTML parseado de la app en modo eval, junto con las URLs javascript: / vbscript: / data:text/html
XSS a través del reporte de errores Los slots de error de los adaptadores escapan err.message; un módulo remoto que lanza HTML no puede inyectarlo en el shell
Ruptura con </script> en el estado de SSR renderStateScript() escapa su payload
Degradación silenciosa del sandbox strictFallback es false por defecto; un mount strict fallido lanza error en vez de convertirse calladamente en eval
Abuso de los overrides de QA Los overrides están apagados fuera de hostnames locales, sujetos a una lista de dominios permitidos, y muestran un banner permanente mientras están activos
Fugas de efectos entre montajes Timers, intervalos, animation frames y listeners se registran y se deshacen al desmontar; en los modos de iframe el teardown destruye el realm
Montaje no autorizado (a nivel de UX) Los roles de RBAC + el principal controlan mount() antes de cualquier efecto secundario

Dicho sin rodeos, porque una mitigación en la que crees pero no tienes es peor que ninguna.

El shell es la raíz confiable. Si un atacante controla el HTML o el JS del shell, ningún framework del lado del cliente puede recuperarse. Todo lo demás asume que el shell está intacto.

Código malicioso en una app — en cualquier modo de sandbox

Sección titulada «Código malicioso en una app — en cualquier modo de sandbox»

Este es el importante.

  • El modo module ejecuta la app en tu ventana con globales compartidos. No hay aislamiento alguno; el modo es un registrador de limpieza.
  • strict y eval ejecutan la app en un iframe del mismo origen. Eso sube el listón —globales separados, teardown garantizado, una fachada wu restringida y congelada, parent/top/frameElement enmascarados— pero se puede escapar desde código del mismo origen:
document.createElement('div').ownerDocument.defaultView.wu // el wu completo

Esa redirección es deliberada: sin ella, React y Vue tropiezan con discrepancias de ownerDocument al insertar nodos en el shadow root. Es un requisito de corrección, y es el techo del aislamiento.

Mismo origen también significa que el iframe puede alcanzar tu localStorage, sessionStorage, cookies e IndexedDB, y puede emitir peticiones con credenciales a tu API.

Ambos sandboxes son contención para código que controlas —aislar globales, garantizar la limpieza— no contención para código adversario. Para eso necesitas una frontera de origen real: un iframe de origen cruzado que controles, o un worker. Mira Apps de terceros.

Wu descarga lo que sea que devuelva la URL. No verifica Subresource Integrity y no calcula hashes. TLS y fijar los orígenes en tu CSP son responsabilidad tuya. El compromiso del CDN de un socio equivale, en modo module, al compromiso de tu propia página.

Un XSS del mismo origen en una app afecta al shadow root de esa app independientemente de Wu, y desde ahí a todo el origen.

RBAC del lado del cliente como barrera de autorización

Sección titulada «RBAC del lado del cliente como barrera de autorización»

wu.can() y el mount() restringido por roles mejoran la corrección y la UX. Corren en el navegador, donde el usuario controla todo. La barrera dura es que tu servidor se niegue a servir el bundle a usuarios no autorizados. Trata el RBAC de wu como una decisión de renderizado, no como una decisión de control de acceso.

Los tokens del bus de eventos como capacidades

Sección titulada «Los tokens del bus de eventos como capacidades»

Los tokens de strictMode son etiquetado de origen. Cualquier código que corra en tu ventana puede leerlos de memoria. Frenan la suplantación accidental y hacen auditable la procedencia; no detienen código que ya se está ejecutando en la página.

Los analizadores estáticos (Socket, Snyk, npm audit, OSV) buscan dos superficies.

Evaluación dinámica de código. Desde v2.7 no hay ni un new Function() ni un eval() en todo src/: ni en el bundle principal, ni en un chunk perezoso, ni en el build UMD. Ambos sitios anteriores se eliminaron en vez de restringirse:

Sitio anterior Qué hacía Reemplazado por
wu-script-executor.js Compilaba cada script de la app con new Function('proxy', code) y lo ejecutaba bajo with(proxy){} Etiquetas <script> corrientes dentro del sandbox del iframe: las ejecuta el navegador
wu-loader.js Compilaba componentes compartidos con new Function('require','module','exports', code) Un import() dinámico nativo

Consecuencias: Wu no necesita script-src 'unsafe-eval', y el aislamiento del modo eval mejoró, pasando de trampas de Proxy sobre el window vivo (escapables vía self/top/parent) a un realm separado de verdad.

Wu sigue ejecutando código que no escribió. La diferencia es que lo ejecuta el navegador, por el mismo camino que cualquier otro <script> de la página, sujeto a tu CSP.

Acceso a la red. Wu hace fetch, deliberadamente:

Módulo Qué descarga Por qué
wu-manifest.js <appUrl>/wu.json El manifiesto de cada app
wu-core.js Sondas HEAD de entries candidatos Resolver el entry cuando no se da prefijo de carpeta
wu-html-parser.js La página HTML de la app Modo eval: extraer scripts y estilos
wu-loader.js Módulos de componentes compartidos wu.use()
wu-ai-provider.js El endpoint de tu proveedor de LLM Trae tu propio LLM; perezoso

No hay telemetría, ni analítica, ni ninguna llamada de vuelta a infraestructura de wu-framework. Cada URL descargada es una que suministró la página anfitriona, vía wu.init({ apps: [{ url }] }) o wu.ai.provider(...).

Audítalo tú mismo en tiempo de ejecución:

const original = window.fetch;
window.fetch = (url, ...rest) => {
console.log('[audit] fetch:', url);
return original(url, ...rest);
};

No abras un issue público. Escribe al mantenedor listado en el campo author de package.json, o abre un aviso de seguridad privado en GitHub.

Incluye una descripción, una reproducción mínima, la versión de wu que probaste y el navegador o runtime.

Acuse de recibo en 48 horas, triaje en una semana, corrección publicada en 30 días en la medida de lo posible. La divulgación se coordina con quien reporta, que recibe crédito salvo que prefiera el anonimato.

Versión Estado
2.7.x soportada
2.6.x solo correcciones de seguridad
< 2.6 sin soporte

Checklist de fortificación — la versión accionable de esta página.