Ir al contenido

Migrar a v2.7

v2.7 es una versión con funcionalidades nuevas. La API de SSR es superficie completamente nueva, así que no rompe nada. Un valor por defecto de comportamiento se invierte y se elimina un módulo interno. Todo lo demás son correcciones de seguridad que solo cambian el comportamiento de código que dependía de un bug.

Ventana de terminal
npm install wu-framework@2.7.2
Cambio Riesgo Acción
strictFallback pasa a false por defecto alto Una app strict con CORS roto ahora lanza error
Se elimina WuScriptExecutor bajo Solo si importabas la subruta directamente
El modo eval ya no evalúa ninguno Puedes quitar unsafe-eval de tu CSP
wu.use() usa import() nativo bajo Las rutas con traversal ahora se rechazan
Los hooks son fail-closed medio Un hook que lanza error ahora cancela
Los roles del manifiesto funcionan de verdad medio El RBAC del manifiesto empieza a aplicarse
Atributos on* eliminados en eval bajo Los handlers inline del HTML de la app dejan de ejecutarse
Corregido el XSS del slot de error ninguno Los mensajes de error se escapan
Los tokens usan WebCrypto ninguno
SSR (wu-framework/server) ninguno Nuevo, opt-in

1. strictFallback ahora es false por defecto

Sección titulada «1. strictFallback ahora es false por defecto»

El cambio con más probabilidad de romperte algo.

Antes de 2.7, una app strict cuyo import() en el iframe fallaba degradaba a eval con un aviso en consola. Ahora mount() lanza:

[strict] iframe import failed for 'cart' and strictFallback is disabled.
Original error: Failed to fetch
Fix: ensure the app's dev server sets Access-Control-Allow-Origin headers,
or explicitly allow degrade with { strictFallback: true } or sandbox: 'eval'.

Por qué cambió. El valor por defecto anterior significaba que cualquiera que pudiera romper CORS podía forzar a una app que tú habías aislado deliberadamente a un modo más débil. Un opt-in explícito se había convertido en un camino accidental. Además, la degradación suele estar rota de todos modos: eval no puede ejecutar módulos ES, así que una app ESM degradada lanza un segundo error, más confuso, que tapa el fallo original de CORS.

Si una app strict que antes funcionaba ahora lanza un error, tenías un problema de CORS desde el principio y estabas corriendo en modo eval sin saberlo. Arregla las cabeceras:

Access-Control-Allow-Origin: https://shell.example.com

Si de verdad quieres el comportamiento antiguo —un bundle UMD de primera parte, de confianza, en desarrollo, por ejemplo— vuelve a activarlo:

await wu.init({
sandbox: 'strict',
strictFallback: true, // global
apps: [{ name: 'legacy', url: '', strictFallback: true }], // o por app
});

Lo de cada app gana sobre lo global.

Audita lo que tienes en realidad antes de actualizar:

// Ejecuta esto en 2.6 en producción. Cualquier app que reporte
// actualMode !== requestedMode lanzará error tras la actualización.
for (const app of wu.inspect().apps) {
const s = app.sandbox;
if (s && s.requestedMode !== s.actualMode) {
console.warn(`${app.name}: asked for ${s.requestedMode}, running ${s.actualMode}`);
}
}

WuScriptExecutor desapareció, junto con el export de subruta wu-framework/sandbox/script-executor y su chunk perezoso.

El modo solía compilar cada script de la app con new Function('proxy', code) y ejecutarlo bajo with(proxy){}. Ahora los scripts se entregan al navegador como etiquetas <script> corrientes dentro del sandbox del iframe, en orden de documento, y el navegador los ejecuta.

Lo que ganas:

  • CSP. Wu ya no necesita script-src 'unsafe-eval' en ningún sitio. No queda ni un new Function ni un eval en src/, ni en ningún bundle, incluido el UMD (que antes tenía que incrustar el evaluador porque no puede cargar chunks de forma asíncrona). Un script externo de una app no necesita ninguna excepción de CSP; uno inline necesita 'unsafe-inline', un requisito mucho más débil.
  • Aislamiento. Un realm separado de verdad, en lugar de trampas de Proxy sobre el window vivo, que se podían escapar vía self, top y parent.

Lo que cambia para ti:

  • Si tu CSP tenía 'unsafe-eval' solo por wu, quítalo.
  • getSandboxInfo() ahora reporta isolationLevel: 'iframe' para las apps en modo eval, en vez de 'proxy-trap'. El valor antiguo se mantiene en la unión de TypeScript por compatibilidad, pero nunca se emite. Actualiza cualquier código que lo compare.
  • Si importabas wu-framework/sandbox/script-executor directamente, ese import ahora falla. No hay reemplazo: la funcionalidad vive dentro del sandbox del iframe.

El modo conserva su nombre: sandbox: 'eval' lo sigue seleccionando.


3. wu.use() carga componentes con import() nativo

Sección titulada «3. wu.use() carga componentes con import() nativo»

WuLoader.loadComponent solía compilar el código descargado con new Function('require', 'module', 'exports', code). Ese camino tenía un shim de require que devolvía {}, así que ningún componente con dependencias funcionó nunca por ahí. Ahora usa un import() dinámico nativo.

Junto con eso se cerró un path traversal. loadComponent es API pública y no validaba su argumento, así que '../../secrets/key.js' resolvía fuera de la app, y el resultado se ejecutaba. Las URLs quedan ahora confinadas bajo la URL base de la app, y se rechazan el traversal (incluidas sus formas codificadas en porcentaje), las URLs absolutas y los esquemas ejecutables.

Acción: si llamabas a wu.use() con una ruta que se escapa de la raíz de la app, ahora lanza error. Esa ruta era un bug; arregla el sitio de la llamada. Los componentes también deben poder cargarse como módulos ES: un componente con forma CommonJS que solo funcionaba gracias al module.exports del shim nunca funcionó realmente.


4. Los hooks de ciclo de vida son fail-closed

Sección titulada «4. Los hooks de ciclo de vida son fail-closed»

Un middleware de hook que lanza un error antes de llamar a next() ahora cancela la operación:

wu.hooks.use('beforeMount', async (ctx, next) => {
await assertAuthorized(ctx.appName); // lanza en un error de red
await next();
});

Antes de 2.7 ese error se registraba y la cadena continuaba, así que un guardián de autenticación que fallaba por un error de red transitorio dejaba montar la app igual. Fail-open, en un guardián.

Acción: revisa cada hook que pueda lanzar. Si un hook es instrumentación y no un guardián, vuelve a activarle el fail-open de forma explícita:

wu.hooks.use('afterMount', reportMetrics, { failOpen: true });

Lanzar después de haber llamado a next() se sigue registrando y no cancela.


5. Los roles RBAC declarados en el manifiesto ahora sí aplican

Sección titulada «5. Los roles RBAC declarados en el manifiesto ahora sí aplican»

WuManifest.normalize() reconstruía el manifiesto desde cero y en el proceso descartaba roles. Así que esto, documentado desde 2.6, no restringía nada:

{ "name": "billing", "entry": "dist/main.js", "wu": { "roles": ["admin"] } }

Ahora funciona. Se leen tanto wu.roles como un roles de nivel superior.

Acción: si el wu.json de alguna app declara roles, montarla empezará a fallar con WU_ACCESS_DENIED salvo que haya un principal establecido y autorizado:

wu.setPrincipal({ role: 'admin', permissions: ['mount:*'] });
wu.can('billing'); // comprueba antes de montar

Audita antes de actualizar:

for (const [name, m] of wu.manifests) {
const roles = m.wu?.roles ?? m.roles;
if (roles?.length) console.log(`${name} will require roles:`, roles);
}

Recuerda que esto es aplicación del lado del cliente. Mejora la corrección y la UX; la barrera dura es que tu servidor se niegue a servir el bundle. Mira RBAC.


6. Los handlers de eventos inline se eliminan del HTML remoto

Sección titulada «6. Los handlers de eventos inline se eliminan del HTML remoto»

En modo eval, las etiquetas <script> se extraían y se revisaban, pero <img src=x onerror="…"> llegaba intacto a innerHTML y se ejecutaba en el realm principal, saltándose tanto la lista de bloqueo de scripts como el sandbox por completo.

Los atributos on* y las URLs javascript: / vbscript: / data:text/html ahora se eliminan del HTML parseado de la app, y se reporta el número de eliminaciones.

Acción: una app en modo eval que dependía de handlers inline en su HTML dejará de responder a ellos. Mueve esa lógica a un script.


7. XSS cerrado en la UI de error de los adaptadores

Sección titulada «7. XSS cerrado en la UI de error de los adaptadores»

Seis adaptadores interpolaban err.message en innerHTML sin escapar. En modo strict, un módulo remoto que lanzaba new Error('<img src=x onerror=…>') enviaba esa cadena tal cual al shell: la app escapándose de su iframe por el canal de reporte de errores. Ahora los mensajes se escapan.

No hay que hacer nada, salvo que estuvieras extrayendo HTML de esos slots de error.


8. Los tokens de autorización usan WebCrypto

Sección titulada «8. Los tokens de autorización usan WebCrypto»

El bus de eventos caía a Math.random() cada vez que faltaba crypto.randomUUID, que es exactamente lo que pasa fuera de un contexto seguro, es decir, en cualquier despliegue interno por HTTP. Ahora crypto.getRandomValues es el escalón intermedio, y Math.random() solo ocurre si WebCrypto no está en absoluto, con un aviso.

No hay que hacer nada.


Superficie completamente nueva. Nada de lo existente cambia.

import { renderApp, renderApps, renderStateScript } from 'wu-framework/server';
const cart = await renderApp('cart', {
module: () => import('./apps/cart/server.js'),
props: { userId },
});

El marcado viaja ya encapsulado en su shadow root mediante Declarative Shadow DOM. Corre en Node a secas: sin vm, sin --experimental-vm-modules.

Adoptarlo es opt-in e incremental:

  1. Añade un módulo de servidor a una app (renderReact, renderVue, renderPreact, renderSvelte, renderSolid o renderHtml).
  2. Renderízalo en el shell y emite renderStateScript(state).
  3. Añade un slot hydrate a la app, o no. Sin él, la app monta encima del marcado del servidor, lo cual es correcto, solo que repintado.

En Astro o Next, cambia WuApp por WuAppSSR en los componentes que importan para SEO o primer pintado. Mira Astro y Next.js y SSR.

También nuevo: renderApps() renderiza en paralelo y aísla los fallos (una app que lanza deja su marcador de posición y aterriza en errors; failFast: true opta por relanzar), además de declarativeShadowDomPolyfill() para navegadores antiguos.


  • Audita getSandboxInfo() en 2.6 buscando apps cuyo actualMode ya difiera de requestedMode: esas lanzarán error en 2.7
  • Arregla CORS en cualquier app que montes con sandbox: 'strict'
  • Quita script-src 'unsafe-eval' de tu CSP si solo estaba por wu
  • Reemplaza las comparaciones contra isolationLevel === 'proxy-trap'
  • Elimina cualquier import de wu-framework/sandbox/script-executor
  • Añade { failOpen: true } a los hooks que son instrumentación, no guardianes
  • Comprueba qué manifiestos declaran wu.roles, y establece un principal para ellos
  • Saca los handlers on* inline del HTML de las apps en modo eval
  • Vuelve a ejecutar tu suite end-to-end con wu.verbose() activado y lee los avisos
Versión Estado
2.7.x soportada
2.6.x solo correcciones de seguridad
< 2.6 sin soporte

El detalle completo está en el CHANGELOG que acompaña al paquete.