Guide CSP : Verrouiller Shopify Hydrogen
Le Cross-Site Scripting (XSS) est la menace n°1 pour le commerce sans tête. Un guide technique pour la mise en œuvre de Strict CSP avec Nonces dans Remix et Hydrogen.
Dans le commerce électronique, la Confiance est la seule devise. Si un client saisit son numéro de carte de crédit sur votre site, il vous fait confiance pour le sécuriser. Mais si vous disposez d’une vulnérabilité Cross-Site Scripting (XSS), un attaquant peut injecter un script qui lit les frappes des touches. L’attaquant récupère la carte de crédit. Vous obtenez le procès.
La défense contre XSS est la Content Security Policy (CSP). C’est une liste blanche. Il indique au navigateur : “Chargez uniquement les scripts de ces domaines. Bloquez tout le reste.”
La mise en œuvre de CSP dans une application à page unique (SPA) comme Shopify Hydrogen (Remix) est notoirement difficile en raison de l’hydratation et des balises tierces. Ceci est le guide définitif pour bien faire les choses.
Pourquoi Maison Code en parle
Chez Maison Code Paris, nous agissons comme la conscience architecturale de nos clients. Nous héritons souvent de stacks “modernes” construites sans compréhension fondamentale de l’échelle.
Nous abordons ce sujet car il représente un point de pivot critique dans la maturité de l’ingénierie. Une mise en œuvre correcte différencie un MVP fragile d’une plateforme résiliente de niveau entreprise.
1. La stratégie : CSP strict avec des noms occasionnels
Autrefois, nous utilisions la « liste blanche de domaines ».
script-src 'soi' https://google-analytics.com https://facebook.com
Ce n’est pas sûr. Si Google Analytics présente une vulnérabilité Open Redirect, l’attaquant peut contourner votre CSP.
La norme moderne est Nonces (numéro utilisé UNE FOIS).
- Le serveur génère un jeton cryptographique aléatoire (
abc123yz) pour chaque requête. - Le serveur ajoute un en-tête :
Content-Security-Policy: script-src 'nonce-abc123yz'. - Le code HTML inclut le jeton :
<script nonce="abc123yz">.
Si un attaquant injecte <script>alert(1)</script>, il n’a aucun cas occasionnel. Le navigateur le bloque.
2. Implémentation dans Remix (Hydrogen)
Dans Remix, nous devons générer le nom occasionnel dans le chargeur racine et le transmettre.
Étape 1 : Générer un nom occasionnel dans entry.server.tsx
// app/entry.server.tsx
importer { generateNonce } depuis './utils' ; // crypto.randomBytes(16).toString('base64')
exporter la fonction par défaut handleRequest (request, réponseStatusCode, réponseHeaders, remixContext) {
const nonce = generateNonce();
// Définir des directives
constcsp = [
"src par défaut 'self'",
`script-src 'self' 'nonce-${nonce}' https://cdn.shopify.com https://challenges.cloudflare.com`,
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", // unsafe-inline nécessaire pour CSS-in-JS généralement
"données img-src 'self' : https://cdn.shopify.com",
"connect-src 'self' https://monorail-edge.shopifysvc.com https://www.google-analytics.com",
"frame-ancetors 'aucun'", // Anti-Clickjacking
].join('; ');
réponseHeaders.set('Content-Security-Policy', csp);
// Passe le nonce au contexte pour que <Scripts /> puisse l'utiliser
return <RemixServer context={remixContext} url={request.url} nonce={nonce} />;
}
Étape 2 : Attacher un nom occasionnel aux scripts dans root.tsx
// app/root.tsx
importer { useNonce } depuis '@shopify/hydrogen' ;
exporter la fonction par défaut App() {
const nonce = useNonce();
retour (
<html lang="fr">
<tête>
<Méta />
<Liens />
</tête>
<corps>
<Point de vente />
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
</corps>
</html>
);
}
Désormais, chaque balise de script générée par Remix aura légalement le nom occasionnel.
3. La sécurité intégrée « Rapport uniquement »
Déployer un CSP fait peur. Si vous oubliez un domaine (par exemple, « fonts.gstatic.com »), votre site tombe en panne. Solution : Mode rapport uniquement.
- Définissez l’en-tête « Content-Security-Policy-Report-Only ».
- Le navigateur chargera les ressources mais enregistrera la violation sur la console (et sur un point de terminaison).
- Exécutez ceci pendant 2 semaines. Surveiller les journaux.
- Une fois les journaux nettoyés, passez en mode d’application.
4. Rapports : utilisation de Sentry
La lecture des violations CSP dans la console est inutile pour les utilisateurs de production.
Vous avez besoin d’un point de terminaison de collecte.
Sentry (et Datadog) prennent en charge les valeurs-clés de CSP Reporting.
report-uri https://o450.ingest.sentry.io/api/.../security/?sentry_key=...;
Lorsqu’un utilisateur au Brésil déclenche une violation CSP (peut-être une extension de malware), Sentry vous alerte.
Filtrage du bruit : vous verrez beaucoup de bruit provenant des extensions de navigateur (LastPass provoque des violations). Ignorez les schémas « moz-extension » et « chrome-extension ». Concentrez-vous sur les injections « http ».
5. Intégrité des sous-ressources (SRI)
Et si le CDN était piraté ?
Si vous chargez « https://code.jquery.com/jquery.min.js » et qu’un pirate informatique modifie ce fichier sur le CDN, il contourne votre CSP (car le domaine est sur liste blanche).
Correction : utilisez SRI.
<script src="..." intègre="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux..." crossorigin="anonymous"></script>
Le navigateur hache le fichier téléchargé. S’il ne correspond pas à l’attribut « intégrité », il refuse de s’exécuter.
Cela garantit que le code ne peut pas changer sans que vous déployiez un nouveau fichier HTML.
6. L’enfer des tiers (Google Tag Manager)
Le marketing adore GTM. La sécurité déteste GTM. GTM injecte des scripts de manière dynamique. GTM propage-t-il le nonce ? Oui, si configuré correctement.
Dans votre extrait GTM, vous devez inspecter le « nonce » du script parent et le transmettre.
Cependant, la plupart des pixels tiers (Facebook) n’en sont pas conscients.
Compromis : vous devez souvent mettre leurs domaines sur liste blanche dans script-src en plus du nom occasionnel.
script-src 'self' 'nonce-...' https://connect.facebook.net ...
5. Prévenir le détournement de clics (ancêtres du cadre)
Le détournement de clics se produit lorsqu’un attaquant intègre votre site dans un « ). Ils ont placé un bouton invisible « Gagner un iPhone » au-dessus de votre bouton « Acheter maintenant ». L’utilisateur pense qu’il clique sur “Gagner”, mais il clique sur “Acheter”.
Correction : frame-ancetors 'aucun'.
Cela interdit à quiconque d’iframer votre site.
Si vous devez être iframe (par exemple, par un partenaire), ajoutez-les à la liste blanche : frame-ancestors 'self' https://partner.com.
6. CSP violé ? Ce qui se produit?
Lorsqu’un CSP est violé, le navigateur renvoie une erreur simplifiée dans la console.
Refusé de charger le script depuis 'http://evil.com/hack.js' car il viole la directive de politique de sécurité du contenu suivante : "script-src 'self'...".
Pour l’utilisateur, le script malveillant ne parvient tout simplement pas à s’exécuter. Le reste du site fonctionne bien. L’attaque est neutralisée en silence.
7. Types de confiance (l’avenir de la défense XSS)
CSP bloque le chargement de scripts malveillants.
Trusted Types bloque l’écriture de code malveillant.
Cela vous oblige à désinfecter les chaînes avant qu’elles ne touchent le DOM.
element.innerHTML = dirtyString; -> Erreur de navigateur bloqué.
Vous devez l’emballer :
element.innerHTML = DOMPurify.sanitize(dirtyString);
Cela crée un objet « Politique ». Le navigateur garantit que seules les chaînes créées par une stratégie peuvent toucher le DOM.
Il détruit une classe entière de vulnérabilités XSS basées sur DOM.
8. Sécurité des travailleurs Web
Les travailleurs (Service Workers, Web Workers) sont isolés. Mais ils peuvent quand même être dangereux (Crypto Mining). Vous devez les restreindre via des directives spécifiques :
worker-src 'self' blob:;: autorisez uniquement les travailleurs de votre domaine.child-src 'self': restreint les iframes et les travailleurs. Les attaquants adorent lancer un Worker en arrière-plan vers d’autres sites ou exploiter du Bitcoin. Votre CSP arrête cela.
10. CSP pour WebAssembly (WASM)
Si vous utilisez WASM (par exemple, pour redimensionner des images côté client), vous avez besoin de directives spéciales.
script-src 'unsafe-eval' (Pour la compilation WASM) est dangereux.
Prise en charge des navigateurs modernes : script-src 'wasm-unsafe-eval'.
Cela permet à WASM de compiler sans ouvrir la porte à JavaScript eval().
Il maintient les parties « Bad Good » séparées des parties « Bad Bad ».
11. Le cycle de vie des exceptions (portée de la demande)
Un Nonce est inutile s’il est statique.
Si vous codez en dur nonce="123" dans votre code HTML, un attaquant injecte simplement <script nonce="123">.
Le nom occasionnel DOIT être généré par demande.
Modèle de remix :
entry.server.tsx: Généreznonce = crypto.randomUUID().- Passez à
<RemixServer nonce={nonce}>. - Hydratez le client avec
<Scripts nonce={nonce}>. Cela garantit que si j’actualise la page, j’obtiens un nouveau nom occasionnel. L’attaquant ne peut pas le deviner.
12. Styles vs Scripts (« unsafe-inline »)
Nous autorisons souvent style-src 'unsafe-inline'.
Pourquoi?
Parce que les bibliothèques CSS-in-JS (Emotion, Styled Components) injectent des balises <style> au moment de l’exécution.
Est-ce une vulnérabilité ?
Techniquement, oui (Exfiltration CSS).
Mais le risque est bien moindre que XSS.
Si vous le pouvez, utilisez une bibliothèque comme Vanilla Extract (Zero-Runtime CSS) qui génère des fichiers statiques .css.
Ensuite, vous pouvez supprimer “unsafe-inline’` et atteindre CSP Nirvana.
13. Pourquoi Maison Code ?
Chez Maison Code, nous pensons que La sécurité est une réputation. Un hack ne coûte pas seulement de l’argent ; cela coûte de la confiance. Nous ne nous contentons pas de « Ça marche ». Nous exigeons “C’est sécurisé”. Nous mettons en œuvre des CSP Crypto-Nonce stricts pour chaque client. Nous surveillons les violations (Sentry) et corrigeons les failles avant qu’elles ne soient exploitées. Nous dormons bien la nuit car nous savons que le navigateur applique nos règles.
12. Conclusion
Un CSP strict est la marque d’une équipe d’ingénierie mature. Cela montre que vous comprenez l’environnement hostile du Web. Il protège vos clients contre Magecart, les enregistreurs de frappe et l’exfiltration de données. C’est difficile à mettre en place, mais obligatoire pour toute marque capturant des données de paiement. N’attendez pas la brèche. Verrouillez la porte maintenant.
Votre magasin est-il vulnérable ?
Nous effectuons des tests d’intrusion et la mise en œuvre de CSP pour les marques Shopify Plus. Engagez nos Architectes.