GeoLeaf Security Contract v2.0.0
Date : 21 mars 2026 Version : 2.0.0 — Audit sécurité GeoLeaf MapLibre GL JS
Ce document est la reference de securite pour la migration MapLibre. Chaque vecteur d'injection identifie, la methode de sanitisation utilisee et le fichier de test correspondant sont listes ci-dessous. Apres chaque sprint de migration, verifier que tous les vecteurs listes ont toujours un test qui passe.
1. Inventaire des vecteurs d'injection
1.1 Vecteurs DOM (injection HTML/SVG dans le DOM)
| Vecteur | Fichier source | Sanitisation | Fichier test |
|---|---|---|---|
| Popup POI (GeoJSON features) | geojson/popup-tooltip.ts | _escapeHtml() (textContent→innerHTML read) | xss-injection-vectors.test.js, security.test.js |
| Tooltip POI (GeoJSON features) | geojson/popup-tooltip.ts | _escapeHtml() | xss-injection-vectors.test.js |
| Labels GeoJSON | labels/label-renderer.ts | textContent (safe) | labels.test.js |
| Noms de couches (layer manager) | layer-manager/section-renderer.ts | DOMSecurity.setSafeHTML() | layer-manager.test.js |
| Categories filtres | ui/filter-panel/*.ts | DOMSecurity.setTextContent() | filter-panel.test.js |
| Resultats recherche | ui/content-builder/core.ts | escapeHtml() par champ | content-builder.test.js |
| Icones toolbar mobile | ui/mobile-toolbar-pill.ts:258 | DOMSecurity.setSafeHTML() + whitelist SVG | xss-injection-vectors.test.js |
| Labels selecteur theme | themes/theme-selector-*.ts | textContent | theme-selector.test.js |
| Barre recherche permalink | permalink/permalink-manager.ts | element.value (pas innerHTML) | permalink-injection.test.js |
| Boutons delete POI | poi/renderers/field-renderers.ts | textContent | xss-prevention.test.js |
| Fleches navigation | ui/mobile-toolbar-pill.ts:271,301 | Constantes SVG hardcodees | N/A (safe by design) |
| Banniere iOS PWA | pwa/ios-banner.ts | Constantes SVG + i18n hardcodes | N/A (safe by design) |
1.2 Vecteurs URL (injection via protocole ou parametres)
| Vecteur | Sanitisation | Fichier test |
|---|---|---|
URLs POI (champs url, website, image, photo, icon) | validateUrl() — whitelist protocole (http/https/data:image) | security.test.js, xss-injection-vectors.test.js |
| Data URLs | _validateDataUrl() — whitelist MIME (image/* uniquement) | security.test.js |
| Permalink lat/lng/zoom | validateNumber() + validateCoordinates() | permalink-injection.test.js |
| Permalink layer IDs | Filtrage string + cap 100 entrees | permalink-injection.test.js |
| Permalink texte filtre | Troncature a 200 caracteres (MAX_TEXT_LEN) | permalink-injection.test.js |
| Permalink compact (base64) | JSON.parse(atob()) + _validateRaw() re-validation | permalink-injection.test.js |
| Permalink rating | Number() + validation > 0 | permalink-injection.test.js |
1.3 Vecteurs prototype pollution
| Vecteur | Protection | Fichier test |
|---|---|---|
| Profil JSON config | _safeAssign() bloque __proto__, constructor, prototype | prototype-pollution.test.js |
| Proprietes POI | sanitizePoiProperties() + _safeAssign() | prototype-pollution.test.js, security.test.js |
| Permalink compact base64 | JSON.parse() (safe) + _validateRaw() type checks | permalink-injection.test.js |
| GeoJSON feature properties | normalizePoiArray() via Object.create(null) | prototype-pollution.test.js |
2. API du module Security
Fonctions de sanitisation
| Fonction | Entree | Garantie sortie | Usage |
|---|---|---|---|
escapeHtml(str) | String quelconque | Caracteres HTML (<, >, &, ", ') echappes en entites | Contenu texte dans le DOM |
escapeAttribute(str) | String quelconque | Memes caracteres + ' echappes | Valeurs d'attributs HTML |
sanitizePoiProperties(props) | Objet POI brut (GeoJSON) | Champs texte echappes, URLs validees, fonctions supprimees | Donnees POI externes |
containsDangerousHtml(str) | String quelconque | true si patterns XSS detectes | Detection/rejet rapide |
stripHtml(html) | String HTML | Texte brut sans aucun tag | Affichage texte pur |
sanitizeSvgContent(svg) | String SVG brute | SVGElement sans <script>, <foreignObject>, handlers on*, javascript: href | Icones SVG externes |
parseHtmlSafely(html, tags) | String HTML + whitelist tags | DocumentFragment avec uniquement les tags autorises | Contenu riche controle |
sanitizeHTML(el, html, opts) | Element DOM + HTML | Injection sanitisee via parseHtmlSafely | Wrapper principal |
validateUrl(url, base, opts) | String URL | URL validee (protocole whiteliste) ou throw | Liens, images, medias |
validateCoordinates(lat, lng) | Nombres | Tuple [lat, lng] valide ou throw | Coordonnees carte |
validateNumber(val, min, max) | Valeur quelconque | Nombre fini dans [min, max] ou null | Parametres numeriques |
Fonctions DOM securisees
| Fonction | Entree | Garantie | Usage |
|---|---|---|---|
DOMSecurity.setTextContent(el, text) | Element + texte | Assignation via textContent (jamais innerHTML) | Tout texte non-HTML |
DOMSecurity.setSafeHTML(el, html, tags?) | Element + HTML + whitelist optionnelle | Passe par Security.sanitizeHTML(), fallback textContent | HTML controle |
DOMSecurity.clearElement(el) | Element | Suppression enfants via removeChild loop | Nettoyage DOM |
DOMSecurity.createElement(tag, attrs, children) | Tag + attributs + enfants | Element cree via DOM API safe | Creation elements |
DOMSecurity.createSVGIcon(w, h, path, opts) | Dimensions + path data | SVGElement via createElementNS (pas innerHTML) | Icones SVG internes |
Protection CSRF
| Fonction | Description |
|---|---|
CSRFToken.init() | Genere un token crypto-random (32 octets, base64url) |
CSRFToken.getToken() | Retourne le token courant, regenere si expire |
CSRFToken.validateToken(token) | Validation constante-time du token |
CSRFToken.rotateToken() | Rotation manuelle + event geoleaf:csrf:rotated |
CSRFToken.addTokenToHeaders(opts) | Ajoute X-CSRF-Token aux headers |
CSRFToken.addTokenToForm(form) | Ajoute <input type="hidden" name="csrf_token"> |
CSRFToken.setSecureCookie(name, val, opts) | Cookie avec Secure, SameSite, HttpOnly |
3. Patterns dangereux — resultat audit
| Pattern | Occurrences | Statut |
|---|---|---|
eval() | 0 | OK |
new Function( | 0 | OK |
setTimeout(string, ...) | 0 | OK |
setInterval(string, ...) | 0 | OK |
document.write | 0 | OK |
insertAdjacentHTML | 0 | OK |
outerHTML (ecriture) | 0 | OK (1 lecture dans label-renderer.ts — safe) |
innerHTML (total) | 31 dans 14 fichiers | Tous safe — voir section 3.1 |
3.1 Classification innerHTML
- Escape pattern (textContent→innerHTML read) :
security/index.ts,poi/normalizers.ts,geojson/popup-tooltip.ts,renderers/abstract-renderer.ts— 5 occ. - DOMSecurity.setSafeHTML wrapper :
utils/helpers/dom-helpers.ts,utils/general/dom-helpers.ts,ui/mobile-toolbar-pill.ts:258— 16 occ. - Clearing (
innerHTML = "") :ui/mobile-toolbar.ts,ui/mobile-toolbar-sheet.ts— 2 occ. - Constantes hardcodees (SVG/HTML entities) :
pwa/ios-banner.ts,themes/theme-selector-primary.ts,ui/mobile-toolbar-pill.ts:271,301— 7 occ. - Commentaire seul :
ui/filter-panel/lazy-loader.ts— 1 occ.
4. Compatibilite CSP (Content Security Policy)
Directives minimales requises pour GeoLeaf + MapLibre GL JS v5 :
| Directive | Valeur | Raison |
|---|---|---|
script-src | 'self' | Zero eval/Function/inline scripts. Workers meme-origine. |
style-src | 'self' 'unsafe-inline' | element.style.* dynamique (15 occ. dans 6 fichiers) + MapLibre GL JS le requiert |
img-src | 'self' data: https: | Data URLs pour markers/icones, tuiles HTTPS |
connect-src | 'self' https: | Fetch GeoJSON, profils, URLs de tuiles |
worker-src | 'self' blob: | GeoJSON worker via blob URLs (worker-manager.ts) |
font-src | 'self' | Aucune fonte externe chargee par le core |
default-src | 'self' | Fallback securise |
Points notables :
unsafe-evalnon requis — confirme pour MapLibre GL JS v5 et GeoLeafunsafe-inlinerequis pourstyle-srcuniquement — contrainte MapLibre GL JS (manipulationelement.style)- Apres migration MapLibre : verifier si MapLibre GL exige des directives supplementaires (WebGL, workers)
5. Checklist migration MapLibre
A verifier apres chaque sprint de migration :
- [ ] Popup rendering passe encore par
escapeHtmlou equivalent MapLibre - [ ] Tooltip rendering passe encore par
escapeHtml - [ ] Markers MapLibre GL ne contournent pas
DOMSecurity - [ ] Labels MapLibre GL utilisent
textContent(pasinnerHTML) - [ ] Nouvelles surfaces d'injection MapLibre documentees dans ce contrat
- [ ]
sanitizeSvgContentcouvre les icones MapLibre si format SVG - [ ]
validateUrlcouvre les URLs de tuiles MapLibre - [ ] CSP : verifier si MapLibre GL exige
unsafe-evalouunsafe-inlinesupplementaire - [ ] Prototype pollution : verifier les nouveaux points d'entree de configuration MapLibre
- [ ] Tous les tests
__tests__/security/passent apres chaque module migre
6. Fichiers de test securite
| Fichier | Tests | Couverture |
|---|---|---|
security/security.test.js | 52 | escapeHtml, escapeAttribute, validateUrl, sanitizePoiProperties |
security/csrf-token.test.js | 23 | CSRFToken lifecycle complet |
security/security-comprehensive.test.js | ~60 | Couverture etendue escapeHtml, coordinates |
security/security-extended.test.js | ~50 | sanitizeSvgContent, validateNumber, parseHtmlSafely |
security/security.esm.test.js | ~70 | Tests ESM de toutes les fonctions |
security/prototype-pollution.test.js | 16 | _safeAssign, normalizePoiArray, GeoJSON properties |
security/xss-prevention.test.js | ~20 | Patterns DOM (textContent vs innerHTML) |
security/xss-injection-vectors.test.js | ~90 | Sprint 5 — 16 payloads OWASP × 10 vecteurs |
security/permalink-injection.test.js | ~30 | Sprint 5 — Injection URL params + compact mode |
security/file-validator.test.js | ~25 | Upload securise (taille, extension, MIME) |
utils/dom-security.test.js | 24 | DOMSecurity wrapper complet |
Total : ~440 tests securite
