Files
ben-to/docs/security-headers.md
2026-07-21 16:50:58 +02:00

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)