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.
npm install wu-framework@2.7.2De un vistazo
Sección titulada «De un vistazo»| 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 fetchFix: 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.comSi 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}`); }}2. sandbox: 'eval' ya no evalúa nada
Sección titulada «2. sandbox: 'eval' ya no evalúa nada»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 unnew Functionni unevalensrc/, 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
windowvivo, que se podían escapar víaself,topyparent.
Lo que cambia para ti:
- Si tu CSP tenía
'unsafe-eval'solo por wu, quítalo. getSandboxInfo()ahora reportaisolationLevel: '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-executordirectamente, 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 montarAudita 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.
9. Nuevo: renderizado en el servidor
Sección titulada «9. Nuevo: renderizado en el servidor»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:
- Añade un módulo de servidor a una app (
renderReact,renderVue,renderPreact,renderSvelte,renderSolidorenderHtml). - Renderízalo en el shell y emite
renderStateScript(state). - Añade un slot
hydratea 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.
Checklist de actualización
Sección titulada «Checklist de actualización»- Audita
getSandboxInfo()en 2.6 buscando apps cuyoactualModeya difiera derequestedMode: 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
Versiones soportadas
Sección titulada «Versiones soportadas»| 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.
