Apps de terceros
“App de terceros” cubre dos situaciones muy distintas, y Wu solo puede ayudarte con una de ellas.
| Código de otro equipo | Código de otra empresa | |
|---|---|---|
| Puedes leerlo | normalmente sí | normalmente no |
| Puede romper el tuyo por accidente | ✅ | ✅ |
| Puede atacarte a propósito | no | asume que sí |
| Un modo de sandbox de wu alcanza | ✅ | ❌ |
Si estás en la columna izquierda —un equipo socio, un desarrollo de una agencia,
el frontend de un producto adquirido— sandbox: 'strict' es la herramienta
correcta. Si estás en la columna derecha, sigue leyendo hasta el final: ningún
modo de sandbox de wu contiene código hostil, y creer lo contrario es lo más
peligroso que podrías llevarte de esta página.
Caso 1: la app de otro equipo
Sección titulada «Caso 1: la app de otro equipo»El riesgo son los accidentes: un global que sobrescriben, un prototipo que parchean, un intervalo que olvidan limpiar, un crash que se lleva la página por delante.
await wu.init({ apps: [ { name: 'partner-widget', url: 'https://partner.example.com', sandbox: 'strict', strictFallback: false, // el valor por defecto desde v2.7 — sé explícito igual }, ],});
await wu.mount('partner-widget', '#widget');
const info = wu.getSandboxInfo('partner-widget');if (info?.actualMode !== 'strict') { throw new Error(`Expected iframe isolation, got ${info?.actualMode}`);}Lo que eso te da:
- Globales separados. Su
window.Reactno es tuwindow.React. Sus parches de prototipos se quedan en su realm. - Teardown garantizado. Destruir el iframe mata el realm, y además Wu limpia cada timer registrado con las propias funciones de limpieza del iframe.
- Un
wurestringido. Dentro del iframe,window.wues una fachada congelada con la API pública solamente: sinwu.core, sinwu.cache, sinwu.pluginSystem. - Contención de CSS. Sus estilos no pueden llegar a tu página, y con
styleMode: 'isolated'los tuyos no pueden llegar a la suya.
Combínalo con el resto de la superficie de contención:
await wu.init({ apps: [{ name: 'partner-widget', url: '…', sandbox: 'strict', roles: ['staff'] }], overrides: { enabled: false }, // sin redirección por cookie en producción});
wu.eventBus.enableStrictMode();wu.eventBus.registerApp('partner-widget', { permissions: ['partner:*'], // solo puede emitir en su propio namespace});Los permisos de eventos son la parte infrautilizada. Por defecto registerApp
concede ['*']; restringirlo significa que el widget no puede emitir
user:logout y disparar el cierre de tu sesión.
Caso 2: código en el que no confías
Sección titulada «Caso 2: código en el que no confías»Aquí está el límite honesto.
strict y eval ejecutan el código de la app en un iframe del mismo
origen, y el document.createElement de ese iframe se redirige
deliberadamente al documento anfitrión; si no, React y Vue tropiezan con
discrepancias de ownerDocument al insertar nodos en el shadow root. Lo que
significa:
// Desde dentro de cualquier sandbox de wu:document.createElement('div').ownerDocument.defaultView.wu // el wu realWu enmascara window.parent, window.top y window.frameElement, pero este
camino está abierto por diseño. Mismo origen es mismo origen: el iframe también
puede alcanzar localStorage, sessionStorage, las cookies y IndexedDB de tu
origen, y puede hacer peticiones con credenciales a tu API.
Un modo de sandbox de wu es contención para código que controlas. No es una frontera de seguridad. Mira el modelo de amenazas.
Cómo se ve una frontera de verdad
Sección titulada «Cómo se ve una frontera de verdad»Un iframe de origen cruzado. No “otra ruta”, no “otro puerto”: un origen distinto cuyo HTML controlas tú.
<!-- En el shell, en app.example.com --><iframe src="https://sandbox.example.net/host.html?app=untrusted" sandbox="allow-scripts" referrerpolicy="no-referrer" allow="" style="border:0;width:100%;height:400px"></iframe>Con sandbox="allow-scripts" y sin allow-same-origin, el frame obtiene un
origen opaco: sin acceso a tus cookies, tu almacenamiento, tu DOM ni tu
window.wu. Ojo: allow-scripts junto con allow-same-origin echa por tierra
todo el ejercicio, porque el frame puede entonces quitarse su propio atributo
sandbox.
Sirve sandbox.example.net desde un origen aparte (un dominio separado es mejor
que un subdominio, porque las cookies pueden alcanzar el dominio padre), y
ejecuta wu dentro de ese frame si también quieres el modelo de micro-apps
allí.
Después construye el puente de forma deliberada:
// Lado del shell — valida cada mensaje, sin excepcionesconst FRAME_ORIGIN = 'https://sandbox.example.net';
window.addEventListener('message', (event) => { if (event.origin !== FRAME_ORIGIN) return; if (event.source !== frame.contentWindow) return;
const msg = event.data; if (msg?.type !== 'cart:add' || typeof msg.sku !== 'string') return;
// Vuelve a autorizar de este lado. Lo que afirma el frame es entrada, no permiso. if (!currentUser.canAddToCart) return; cart.add(msg.sku);});El puente es el modelo de seguridad. Cada capacidad que tiene el código no confiable es un tipo de mensaje que tú decidiste implementar, comprobado contra permisos que evalúas del lado confiable. Nada es implícito.
Añade una CSP que limite adónde puede ir el frame:
Content-Security-Policy: frame-src https://sandbox.example.net; script-src 'self';Cadena de suministro, sea cual sea el modo
Sección titulada «Cadena de suministro, sea cual sea el modo»Incluso un socio que se porta bien publica desde un servidor que tú no operas. Wu no verifica hashes de bundles: descarga lo que sea que devuelva la URL.
- Sirve todo por TLS y fija los orígenes exactos en tu CSP.
- Considera Subresource Integrity si puedes controlar la URL de entrada y esta lleva hash de contenido. Wu no calculará ni comprobará SRI por ti.
- Trata el compromiso del CDN de un socio como equivalente al compromiso de tu
propia página, porque en modo
modulees exactamente eso.
Cómo elegir
Sección titulada «Cómo elegir»¿El código es adversario, o solo ajeno?│├─ Ajeno (otro equipo, una agencia, un proveedor con el que tienes contrato)│ → sandbox: 'strict', strictFallback: false│ → styleMode: 'isolated' en el wu.json de la app│ → eventBus.registerApp con permisos estrechos│ → verifica con getSandboxInfo()│└─ Adversario (plugins enviados por usuarios, un marketplace no confiable, cualquier cosa que no ejecutarías en tu propio build) → iframe de origen cruzado, sandbox="allow-scripts" (sin allow-same-origin) → puente postMessage con validación de origen + source + forma → CSP frame-src fijando el origen del sandbox → wu es opcional dentro de ese frame; no es él la fronteraUna checklist para el caso de código ajeno
Sección titulada «Una checklist para el caso de código ajeno»-
sandbox: 'strict'puesto,strictFallbackdejado enfalse -
getSandboxInfo()verificado después del mount, también en producción -
styleMode: 'isolated'en el manifiesto de la app -
eventBus.enableStrictMode()y permisos estrechos por app -
overrides: { enabled: false }en producción -
rolesde RBAC en todo lo privilegiado, con la comprobación real en el servidor - CSP
connect-src/script-srcfijados a los orígenes que esperas - El origen de la app envía cabeceras CORS, para que
strictno lance error al montar
Mira la Checklist de fortificación para la lista completa.
