Guía de CSP: Bloquear Shopify Hydrogen
Cross-Site Scripting (XSS) es la amenaza número uno para el comercio headless. Una guía técnica para implementar CSP estricta con Nonces en Remix e Hidrógeno.
En el comercio electrónico, la Confianza es la única moneda. Si un cliente ingresa su número de tarjeta de crédito en su sitio, confía en que usted lo protegerá. Pero si tiene una vulnerabilidad de Cross-Site Scripting (XSS), un atacante puede inyectar un script que lea las pulsaciones de teclas. El atacante obtiene la tarjeta de crédito. Obtienes la demanda.
La defensa contra XSS es la Política de seguridad de contenido (CSP). Es una lista blanca. Le dice al navegador: “Cargue sólo scripts de estos dominios. Bloquee todo lo demás”.
Implementar CSP en una aplicación de página única (SPA) como Shopify Hydrogen (Remix) es notoriamente difícil debido a la hidratación y las etiquetas de terceros. Esta es la guía definitiva para hacerlo bien.
Por qué Maison Code habla de esto
En Maison Code Paris, actuamos como la conciencia arquitectónica de nuestros clientes. A menudo heredamos stacks “modernos” construidos sin una comprensión fundamental de la escala.
Discutimos este tema porque representa un punto de inflexión crítico en la madurez de la ingeniería. Implementarlo correctamente diferencia un MVP frágil de una plataforma resistente de nivel empresarial.
1. La estrategia: CSP estricto con Nonces
En los viejos tiempos, usábamos “Lista blanca de dominios”.
script-src 'auto' https://google-analytics.com https://facebook.com
Esto es inseguro. Si Google Analytics tiene una vulnerabilidad de Open Redirect, el atacante puede eludir su CSP.
El estándar moderno es Nonces (Número usado UNA VEZ).
- El Servidor genera un token criptográfico aleatorio (
abc123yz) para cada solicitud. - El servidor agrega un encabezado:
Content-Security-Policy: script-src 'nonce-abc123yz'. - El HTML incluye el token:
<script nonce="abc123yz">.
Si un atacante inyecta <script>alert(1)</script>, no tiene nonce. El navegador lo bloquea.
2. Implementación en Remix (Hidrógeno)
En Remix, necesitamos generar el nonce en el cargador raíz y transmitirlo.
Paso 1: Generar Nonce en entry.server.tsx
// aplicación/entry.server.tsx
importar {generarNonce} desde './utils'; // cripto.randomBytes(16).toString('base64')
exportar función predeterminada handleRequest (solicitud, ResponseStatusCode, ResponseHeaders, remixContext) {
const nonce = generarNonce();
// Definir directivas
csp constante = [
"src predeterminado 'auto'",
`script-src 'self' 'nonce-${nonce}' https://cdn.shopify.com https://challenges.cloudflare.com`,
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", // normalmente se necesita unsafe-inline para CSS-in-JS
"datos 'propios' de img-src: https://cdn.shopify.com",
"connect-src 'self' https://monorail-edge.shopifysvc.com https://www.google-analytics.com",
"frame-ancestros 'ninguno'", // Anti-Clickjacking
].join('; ');
ResponseHeaders.set('Política-de-seguridad-de-contenido', csp);
// Pasa nonce al contexto para que <Scripts /> pueda usarlo
return <RemixServer context={remixContext} url={request.url} nonce={nonce} />;
}
Paso 2: adjunte Nonce a los scripts en root.tsx
// aplicación/root.tsx
importar {useNonce} desde '@shopify/hydrogen';
exportar función predeterminada App() {
const nonce = usarNonce();
regresar (
<html lang="es">
<cabeza>
<Meta/>
<Enlaces />
</cabeza>
<cuerpo>
<Salida />
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
</cuerpo>
</html>
);
}
Ahora, cada etiqueta de script generada por Remix tendrá legalmente el nonce.
3. El sistema de seguridad “Sólo informar”
Implementar un CSP da miedo. Si olvida un dominio (por ejemplo, fonts.gstatic.com), su sitio falla.
Solución: Modo de solo informe.
- Establezca el encabezado
Content-Security-Policy-Report-Only. - El navegador cargará los recursos pero registrará la infracción en la consola (y en un punto final).
- Ejecute esto durante 2 semanas. Monitorear registros.
- Una vez que los registros estén limpios, cambie al modo de aplicación.
4. Informes: uso de Sentry
Leer violaciones de CSP en la consola es inútil para los usuarios de producción.
Necesita un punto final de recopilación.
Sentry (y Datadog) admiten valores-clave de informes CSP.
reporte-uri https://o450.ingest.sentry.io/api/.../security/?sentry_key=...;
Cuando un usuario en Brasil desencadena una infracción de CSP (tal vez una extensión de malware), Sentry le avisa.
Filtrado de ruido: Verá mucho ruido en las extensiones del navegador (LastPass provoca infracciones). Ignore los esquemas “moz-extension” y “chrome-extension”. Concéntrese en las inyecciones http.
5. Integridad de los subrecursos (SRI)
¿Qué pasa si piratean la CDN?
Si carga https://code.jquery.com/jquery.min.js y un hacker modifica ese archivo en la CDN, omitirá su CSP (porque el dominio está en la lista blanca).
Solución: utilice SRI.
<script src="..." integridad="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux..." crossorigin="anonymous"></script>
El navegador codifica el archivo descargado. Si no coincide con el atributo “integridad”, se niega a ejecutarse.
Esto garantiza que el código no puede cambiar sin que usted implemente un nuevo archivo HTML.
6. Infierno de terceros (Administrador de etiquetas de Google)
Al marketing le encanta GTM. La seguridad odia a GTM. GTM inyecta scripts dinámicamente. ¿GTM propaga el nonce? Sí, si se configura correctamente.
En su fragmento de GTM, debe inspeccionar el “nonce” del script principal y transmitirlo.
Sin embargo, la mayoría de los píxeles de terceros (Facebook) no lo saben.
Compromiso: A menudo tienes que incluir sus dominios en la lista blanca en script-src además del nonce.
script-src 'self' 'nonce-...' https://connect.facebook.net ...
5. Prevención del clickjacking (antepasados del marco)
El clickjacking ocurre cuando un atacante inserta su sitio en un <iframe> en su sitio (evil.com).
Pusieron un botón invisible “Ganar iPhone” encima del botón “Comprar ahora”.
El usuario cree que está haciendo clic en “Ganar”, pero hace clic en “Comprar”.
Solución: frame-ancestros 'ninguno'.
Esto prohíbe a cualquiera hacer iframes en su sitio.
Si necesita que lo incluyan en un iframe (por ejemplo, un socio), inclúyalo en la lista blanca: frame-ancestors 'self' https://partner.com.
6. ¿Se ha violado el CSP? ¿Lo que sucede?
Cuando se viola un CSP, el navegador arroja un error simplificado en la consola.
Se negó a cargar el script desde 'http://evil.com/hack.js' porque viola la siguiente directiva de la Política de seguridad de contenido: "script-src 'self' ...".
Para el usuario, el script malicioso simplemente no se ejecuta. El resto del sitio funciona bien. El ataque es neutralizado en silencio.
7. Tipos confiables (el futuro de la defensa XSS)
CSP bloquea la carga de scripts maliciosos.
Tipos de confianza bloquea la escritura de código malicioso.
Te obliga a desinfectar las cadenas antes de que toquen el DOM.
element.innerHTML = dirtyString; -> Error de navegador bloqueado.
Debes envolverlo:
elemento.innerHTML = DOMPurify.sanitize(dirtyString);
Esto crea un objeto “Política”. El navegador garantiza que solo las cadenas creadas por políticas pueden tocar el DOM.
Destruye una clase completa de vulnerabilidades XSS basadas en DOM.
8. Seguridad del trabajador web
Los trabajadores (trabajadores de servicios, trabajadores web) están aislados. Pero aún así pueden ser peligrosos (Crypto Mining). Debes restringirlos mediante directivas específicas:
worker-src 'self' blob:;: Solo permite trabajadores de su dominio.child-src 'self': Restringe iframes y trabajadores. A los atacantes les encanta activar un trabajador en segundo plano en otros sitios DDOS o extraer Bitcoin. Su CSP detiene esto.
10. CSP para WebAssembly (WASM)
Si usa WASM (por ejemplo, para cambiar el tamaño de imágenes en el lado del cliente), necesita directivas especiales.
script-src 'unsafe-eval' (para compilación WASM) es peligroso.
Los navegadores modernos admiten: script-src 'wasm-unsafe-eval'.
Esto permite que WASM se compile sin abrir la puerta a JavaScript eval().
Mantiene las partes “Malas Buenas” separadas de las partes “Malas Malas”.
11. El ciclo de vida de Nonce (alcance de la solicitud)
Un Nonce es inútil si es estático.
Si codifica nonce="123" en su HTML, un atacante simplemente inyecta <script nonce="123">.
El Nonce DEBE generarse por solicitud.
Patrón de remezcla:
entry.server.tsx: Generanonce = crypto.randomUUID().- Pase a
<RemixServer nonce={nonce}>. - Hidratar el cliente con
<Scripts nonce={nonce}>. Esto garantiza que si actualizo la página, obtendré un nuevo nonce. El atacante no puede adivinarlo.
12. Estilos frente a scripts (‘inseguro-en línea’)
A menudo permitimos style-src 'unsafe-inline'.
¿Por qué?
Porque las bibliotecas CSS-in-JS (Emotion, Styled Components) inyectan etiquetas <style> en tiempo de ejecución.
¿Es esto una vulnerabilidad?
Técnicamente, sí (exfiltración CSS).
Pero tiene un riesgo mucho menor que XSS.
Si puede, utilice una biblioteca como Vanilla Extract (CSS de tiempo de ejecución cero) que genera archivos .css estáticos.
Luego puede eliminar “unsafe-inline” y lograr CSP Nirvana.
13. ¿Por qué Código Maison?
En Maison Code, creemos que La seguridad es reputación. Un hack no sólo cuesta dinero; cuesta confianza. No nos conformamos con “funciona”. Exigimos “Es seguro”. Implementamos CSP estrictos Crypto-Nonce para cada cliente. Supervisamos las infracciones (Sentry) y parcheamos los agujeros antes de que sean explotados. Dormimos bien por la noche porque sabemos que el navegador hace cumplir nuestras reglas.
12. Conclusión
Un CSP estricto es el sello distintivo de un equipo de ingeniería maduro. Demuestra que comprende el entorno hostil de la web. Protege a sus clientes de Magecart, keyloggers y filtración de datos. Es difícil de configurar, pero obligatorio para cualquier marca que capture datos de pago. No espere a que se produzca la infracción. Cierra la puerta ahora.
¿Tu tienda es vulnerable?
Realizamos pruebas de penetración e implementación de CSP para las marcas Shopify Plus. Contrate a nuestros arquitectos.