Aller au contenu
script-src.com
script-src.com
EN / FR
← Tous les guides

Pourquoi votre serveur de développement casse sous script-src

Par Thomas Poyeton

script-src unsafe-eval vite webpack dev-server

Votre politique n'a rien d'anormal. Les serveurs de développement reposent précisément sur ce qu'une directive script-src stricte est censée empêcher : exécuter des chaînes de caractères comme du code et injecter des scripts en ligne. Webpack exécute les modules recompilés via eval. Vite s'en passe, mais injecte un script en ligne pour React Fast Refresh, et les deux poussent leurs mises à jour par une websocket. La production n'a besoin de rien de tout cela : la solution est une courte liste d'autorisations réservées au développement, pas une politique de production affaiblie.

L'erreur eval vient du devtool de webpack

La première chose que l'on voit après avoir ajouté script-src à un projet webpack est une page blanche et ce message :

Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an
allowed source of script in the following Content Security Policy directive:
"script-src 'self'".

Aucun code du projet n'appelle eval. C'est webpack qui le fait. En mode: 'development', l'option devtool vaut eval par défaut : chaque module est compilé en chaîne de caractères puis exécuté avec eval(), avec un commentaire //# sourceURL ajouté pour que les traces d'erreur pointent toujours vers le fichier d'origine. Les recompilations sont rapides parce que seule la chaîne du module modifié est réévaluée. Next.js et Vue CLI butent sur le même mur en développement, car leurs builds de dev passent par le même outillage fondé sur eval.

Vous avez deux portes de sortie. La première : garder le comportement par défaut de webpack et ajouter 'unsafe-eval' à la politique envoyée par votre serveur de développement, et à celle-là uniquement. La seconde : choisir un devtool qui n'utilise pas eval :

// webpack.config.js
module.exports = {
  devtool: 'cheap-module-source-map',
};

Toute valeur commençant par eval- réintroduit l'exigence, et les variantes sans eval recompilent plus lentement sur les gros projets. C'est ce ralentissement qui fait de l'autorisation 'unsafe-eval' limitée au dev le meilleur compromis dans la plupart des cas.

Vite échoue sur un script en ligne

Vite sert vos fichiers source comme des modules ES natifs, donc le code applicatif s'exécute sans eval. Ce qui casse est plus discret et plus dur à diagnostiquer. Avec @vitejs/plugin-react, le serveur de développement injecte dans la page un court script de module en ligne qui installe le runtime React Fast Refresh. Une directive script-src sans 'unsafe-inline' bloque ce script, et le symptôme visible n'est pas une erreur CSP mais ceci :

@vitejs/plugin-react can't detect preamble. Something is wrong.

La vraie cause, un message "Refused to execute inline script", se trouve quelques lignes plus haut dans la console. En développement, le correctif pratique est 'unsafe-inline' dans la politique de dev. Vite 5.3 a ajouté une option html.cspNonce qui appose un attribut nonce sur chaque balise injectée par Vite, mais un nonce n'aide que si quelque chose sert le HTML en y substituant une valeur neuve à chaque réponse, ce qu'un simple vite dev ne fait pas. Vite insère aussi les styles sous forme de balises <style> en ligne pendant le développement, donc style-src a besoin de la même autorisation de dev.

Si les mises à jour à chaud cessent de s'appliquer et que la console montre une connexion refusée vers ws://localhost:5173/, ce n'est pas du tout script-src. Le canal HMR est une websocket, régie par connect-src.

Deux serveurs, donc deux origines

Le montage le plus piégeux est un backend (Rails, Django, Express) qui rend le HTML et envoie l'en-tête CSP, pendant que Vite ou webpack-dev-server sert le JavaScript depuis un autre port. 'self' ne couvre plus vos propres scripts, car un port différent est une origine différente. La politique de dev doit nommer explicitement le serveur de développement :

Content-Security-Policy: script-src 'self' 'unsafe-eval' http://localhost:5173; connect-src 'self' ws://localhost:5173

Remplacez le port par celui de votre serveur de dev (webpack-dev-server utilise 8080 par défaut). Aucune de ces entrées n'a sa place dans l'en-tête de production, ce qui amène au dernier point.

Séparer la politique par environnement, puis vérifier ce qui part en production

Gardez une politique de production stricte et une politique de développement assouplie, choisies à l'endroit où l'en-tête est défini :

const dev = process.env.NODE_ENV !== 'production';

const csp = dev
  ? "script-src 'self' 'unsafe-eval' http://localhost:5173; connect-src 'self' ws://localhost:5173"
  : `script-src 'nonce-${nonce}' 'strict-dynamic'`;

res.setHeader('Content-Security-Policy', csp);

Le scénario d'échec à surveiller est l'expédition en production de la branche dev de cette condition. 'unsafe-eval' en production rouvre l'exécution de chaînes comme du code pour chaque script de la page, alors que rien dans un bundle de production n'en a besoin : webpack comme Vite produisent du code sans eval au moment du build. Avant une mise en production, collez l'en-tête de production dans l'évaluateur de CentralCSP. Il signale 'unsafe-eval' et 'unsafe-inline' dès qu'ils s'échappent du développement.