100 lines
5.5 KiB
Markdown
100 lines
5.5 KiB
Markdown
# En-têtes de sécurité et Content-Security-Policy
|
|
|
|
L'application web est durcie par défaut via le plugin Vite [`apps/web/vite-plugin-csp.ts`](../apps/web/vite-plugin-csp.ts). Il injecte une **Content-Security-Policy** dans le HTML de chaque page (et un `serve.json` en build) et émet plusieurs en-têtes de sécurité complémentaires.
|
|
|
|
## Vue d'ensemble
|
|
|
|
| En-tête | Valeur | Émis par |
|
|
| --- | --- | --- |
|
|
| `Content-Security-Policy` | `default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; frame-src 'none'` | Plugin Vite (dev + build) |
|
|
| `X-Content-Type-Options` | `nosniff` | Plugin Vite |
|
|
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Plugin Vite |
|
|
| `X-Frame-Options` | `DENY` | Plugin Vite |
|
|
| `Permissions-Policy` | `accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()` | Plugin Vite |
|
|
|
|
En **build de production**, le plugin écrit en plus `dist/serve.json` ; le serveur statique `serve` (cf. [`Dockerfile`](../Dockerfile) : `CMD ["serve", "-s", "dist", "-l", "3000"]`) applique les en-têtes déclarés.
|
|
|
|
En **dev** (Vite), le plugin injecte la CSP via `<meta http-equiv="Content-Security-Policy">` **et** pose les en-têtes de réponse via un middleware Connect. La CSP dev assouplit `script-src` et `style-src` avec `'unsafe-inline'` + `'unsafe-eval'`, et `connect-src` avec `ws:` / `wss:` pour ne pas casser HMR.
|
|
|
|
## Adapter la CSP à Matomo
|
|
|
|
Le serveur Matomo est optionnel (cf. [matomo-analytics.md](matomo-analytics.md)). Quand `VITE_MATOMO_URL` est défini au build, son origine est ajoutée à `script-src`, `connect-src` et `img-src` :
|
|
|
|
```bash
|
|
# Build local avec Matomo
|
|
VITE_MATOMO_URL=https://analytics.example.com/ \
|
|
npm run build:web
|
|
```
|
|
|
|
La CSP générée ressemblera à :
|
|
|
|
```
|
|
default-src 'self';
|
|
script-src 'self' 'https://analytics.example.com';
|
|
style-src 'self';
|
|
img-src 'self' 'https://analytics.example.com';
|
|
font-src 'self';
|
|
connect-src 'self' 'https://analytics.example.com';
|
|
media-src 'self';
|
|
object-src 'none';
|
|
base-uri 'self';
|
|
form-action 'self';
|
|
frame-ancestors 'none';
|
|
frame-src 'none'
|
|
```
|
|
|
|
Sans `VITE_MATOMO_URL`, **aucune origine externe** n'est ajoutée : la surface d'attaque est minimale.
|
|
|
|
> **Pourquoi `script-src` autorise `'self'` sans `unsafe-inline` en prod ?** Vite produit un bundle compilé référencé via `<script type="module" src="/assets/...">` (même origine). Aucun script inline n'est généré. Le plugin Matomo injecte un script dynamiquement (cf. [`apps/web/src/infra/matomo.ts`](../apps/web/src/infra/matomo.ts)) : l'URL cible `https://<host Matomo>/matomo.js`, couverte par l'origine ajoutée à `script-src`.
|
|
>
|
|
> **Pourquoi `style-src` autorise `'unsafe-inline'` en prod ?** Solid pose des styles inline (`<span style="...">`) sur des nœuds existants (cf. `SpriteTile.tsx`). La directive `style-src` vise les `<style>` statiques et les `style="..."` du HTML, donc l'autorisation est nécessaire. Si on veut durcir davantage à l'avenir : extraire les styles inline vers des classes CSS et passer à `style-src 'self'` strict, ou utiliser des hash/nonces `style-src-attr 'sha256-…'`.
|
|
|
|
## Vérification manuelle
|
|
|
|
```bash
|
|
# 1) Build et smoke test local
|
|
npm run pipeline:recipes
|
|
npm run build:web
|
|
npx serve -s apps/web/dist -l 3000 &
|
|
sleep 1
|
|
|
|
# 2) Vérifier les en-têtes
|
|
curl -sI http://localhost:3000/ | grep -Ei 'content-security-policy|x-frame|x-content|referrer|permissions'
|
|
|
|
# 3) Vérifier la balise meta dans une page recette
|
|
curl -s http://localhost:3000/r/base%3Aonigiri/variant%3Aonigiri-kimchi-mozza \
|
|
| grep -i 'http-equiv="Content-Security-Policy"'
|
|
|
|
# 4) Vérifier que dist/serve.json est correct
|
|
cat apps/web/dist/serve.json
|
|
```
|
|
|
|
Côté navigateur, onglet **Network** → sélectionner la requête du document → panneau **Headers** : la section « Response Headers » doit afficher les cinq en-têtes ci-dessus. Aucune erreur CSP ne doit apparaître dans la console pour les actions courantes (navigation, partage, fermeture du bandeau cookies).
|
|
|
|
## Tests automatisés
|
|
|
|
- `apps/web/vite-plugin-csp.unit.test.ts` — helpers purs (construction CSP, sérialisation, injection HTML, `serve.json`).
|
|
- `apps/web/vite-plugin-csp.int.test.ts` — scénarios Gherkin `@int-csp-*` (`@int-csp-html-injection`, `@int-csp-matomo-origin`, `@int-csp-serve-json`).
|
|
- `tests/e2e/security-headers.e2e.ts` — vérifie en dev (Playwright + Vite middleware) la présence de la CSP et des en-têtes sur `/` et `/r/...`, et la balise meta dans le HTML rendu.
|
|
|
|
Lancer toute la chaîne :
|
|
|
|
```bash
|
|
npm run check:quality
|
|
npm run test:ci
|
|
```
|
|
|
|
## Hors scope (V1)
|
|
|
|
- Subresource Integrity (SRI) sur les bundles Vite — l'absence de CDN rend l'attaque peu probable, mais à étudier dans un ticket dédié.
|
|
- `report-uri` / `report-to` — pas de collecteur configuré sur la V1.
|
|
- CSP par nonce / hash pour autoriser des styles Solid inline en prod — non requis tant que la directive `style-src 'self'` n'engendre pas de violation observable.
|
|
|
|
## Références
|
|
|
|
- Plugin Vite : [`apps/web/vite-plugin-csp.ts`](../apps/web/vite-plugin-csp.ts)
|
|
- Plugin SEO recette (doit passer par `transformIndexHtml`) : [`apps/web/vite-plugin-recipe-seo.ts`](../apps/web/vite-plugin-recipe-seo.ts)
|
|
- Issue : [#104](https://github.com/kazerlelutin/ben-to/issues/104)
|
|
- Spec : [MDN — Content Security Policy (CSP)](https://developer.mozilla.org/docs/Web/HTTP/CSP)
|
|
- Spec : [MDN — Permissions Policy](https://developer.mozilla.org/docs/Web/HTTP/Headers/Permissions-Policy)
|