Picarones / SECURITY.md
Claude
feat(web): Sprint A4 — sécurité web (B-11 CSRF, M-3 /health)
c9d381c unverified
|
Raw
History Blame
7.44 kB

SECURITY — Picarones

Picarones est conçu pour être déployé dans trois contextes très différents :

  1. Poste développeur (Codespaces, laptop) — accès local uniquement, le garde-fou est ouvert pour fluidifier l'itération.
  2. Serveur d'institution (intranet patrimonial, cluster scientifique) — accès authentifié interne, mais quelques utilisateurs peuvent lancer des benchmarks coûteux ; le serveur doit borner la consommation.
  3. Espace public (HuggingFace Space, démo en ligne) — n'importe quel visiteur peut atteindre l'API ; les clefs serveur (OpenAI, Anthropic, Mistral, Azure…) ne doivent pas être exposées au DoS-financier.

Ce document décrit les contrôles disponibles depuis le Sprint 24 et la configuration recommandée pour chaque cas.


Variables d'environnement de sécurité

Variable Défaut Effet
PICARONES_PUBLIC_MODE non défini Si 1/true : refuse OCR cloud + LLM mutualisés et active rate limit.
PICARONES_BROWSE_ROOTS (auto) Liste de chemins (séparateur : Unix / ; Windows) autorisés pour /api/corpus/browse. Surcharge le défaut.
PICARONES_MAX_UPLOAD_MB 100 Taille max d'une image uploadée.
PICARONES_MAX_CONCURRENT_JOBS 2 Plafond global de benchmarks simultanés (sémaphore en mémoire).
PICARONES_RATE_LIMIT_PER_HOUR 5 (en mode public) Jobs max par IP et par heure. 0 = désactivé.
PICARONES_CSP (politique durcie) Surcharge la Content-Security-Policy envoyée par le middleware.
PICARONES_CSRF_REQUIRED non défini Si 1/true/yes : active la protection CSRF (double-submit cookie + signature HMAC) sur tout POST/PUT/PATCH/DELETE. Voir § « CSRF — déploiement institutionnel » ci-dessous.
PICARONES_CSRF_SECRET (auto) Secret HMAC pour signer les tokens CSRF. Si non défini, généré au démarrage avec un warning ; les tokens sont alors invalidés à chaque redémarrage. À définir en production.

CSRF — déploiement institutionnel (Sprint A4)

L'application embarque un middleware CSRF désactivé par défaut (rétrocompat HuggingFace Space où il n'y a pas de session authentifiée à protéger). Pour un déploiement BnF / Bibliothèque nationale derrière SSO :

export PICARONES_CSRF_REQUIRED=1
export PICARONES_CSRF_SECRET="$(openssl rand -hex 32)"  # 64 chars hex

Comment ça marche : pattern « double-submit cookie ». Le serveur pose un cookie picarones_csrf (httponly=False, samesite=strict) qui contient un token <nonce>.<HMAC-SHA256(secret, nonce)>. Sur tout POST/PUT/PATCH/DELETE non exempt, le client doit renvoyer le même token dans l'en-tête X-CSRF-Token. Le serveur compare en temps constant et vérifie la signature. Une page tierce ne peut pas lire le cookie (samesite=strict + JS d'origine différente) ni produire une signature valide (HMAC), donc ne peut pas forger une requête.

Endpoints exemptés : /health, /api/csrf/token (le endpoint qui donne le token).

Bootstrap d'un client tiers (curl, scripts CI) :

# 1. Récupérer un token et persister le cookie dans un jar
curl -c cookies.txt http://picarones.example/api/csrf/token | jq -r .token

# 2. Réutiliser le token dans le header
TOKEN=$(jq -r .token < <(curl -sb cookies.txt http://.../api/csrf/token))
curl -b cookies.txt -H "X-CSRF-Token: $TOKEN" -X POST .../api/lang/fr

Frontend : le JS embarqué (web-app.js) wrappe fetch() pour injecter automatiquement le header sur toute requête mutante same-origin. Aucun changement requis dans le code applicatif.


Contrôles par contexte

🧑‍💻 Développement (défaut, PICARONES_PUBLIC_MODE non défini)

picarones serve --port 8000
  • Tous les moteurs OCR sont disponibles.
  • /api/corpus/browse voit cwd, ./uploads/, /workspaces, tempdir.
  • Pas de rate limit.
  • CSP appliquée mais permissive (unsafe-inline toléré tant que les templates web ne sont pas Jinja2 — voir Sprint 25).

🏛 Serveur d'institution

export PICARONES_BROWSE_ROOTS="/srv/corpus:/srv/uploads"
export PICARONES_MAX_CONCURRENT_JOBS=4
export PICARONES_MAX_UPLOAD_MB=500
picarones serve --host 0.0.0.0 --port 8000

À combiner avec une terminaison TLS et une authentification au niveau reverse-proxy (nginx + auth basic, ou Keycloak/SAML). Picarones n'embarque pas son propre système d'authentification — c'est un choix conscient pour ne pas réinventer un sous-système qui sera mieux servi par l'infra existante.

🌐 HuggingFace Space / démo publique

ENV PICARONES_PUBLIC_MODE=1
ENV PICARONES_RATE_LIMIT_PER_HOUR=5
ENV PICARONES_MAX_CONCURRENT_JOBS=2
ENV PICARONES_MAX_UPLOAD_MB=50
# Optionnel : surcharger les browse roots
# ENV PICARONES_BROWSE_ROOTS=/data/corpus

Effets en mode public :

  • ❌ Moteurs OCR cloud (mistral_ocr, google_vision, azure_doc_intel) refusés en 403.
  • ❌ Pipelines OCR+LLM (openai, anthropic, mistral, ollama) refusés en 403.
  • /api/corpus/browse se limite à ./uploads/.
  • /api/benchmark/start et /api/benchmark/run rate-limités en 429.
  • 🔒 Content-Security-Policy + X-Frame-Options: DENY + X-Content-Type-Options: nosniff + Referrer-Policy: strict-origin-when-cross-origin sur toutes les réponses.

Contrôles d'upload

Images

  • Validation Pillow systématique : Image.open(...).verify() dans un try/except qui capture les UnidentifiedImageError, DecompressionBombError, et l'exception générique.
  • Limite de taille par fichier (PICARONES_MAX_UPLOAD_MB).
  • Basename forcé : un nom de fichier multipart contenant .. ou / est tronqué à son nom de base avant écriture.

Archives ZIP

  • Bombe ZIP : taille décompressée bornée à 500 Mo, nombre de fichiers borné à 2000.
  • Path traversal : seuls les noms de base sont conservés (les répertoires internes du ZIP sont aplatis).
  • Filtres macOS : les fichiers ._* (AppleDouble) sont ignorés.
  • Symlinks : Python's zipfile n'extrait pas les symlinks par défaut ; un check explicite (ZipInfo.external_attr & 0xA000) est sur la roadmap comme défense en profondeur.

Modèle de menace

Menace Mitigation
Visiteur consomme la clef API mainteneur PICARONES_PUBLIC_MODE=1 → 403 sur LLM/OCR cloud.
DoS via 50 benchmarks concurrents PICARONES_MAX_CONCURRENT_JOBS (sémaphore) + rate limit par IP.
Bombe Pillow (CVE-2023-50447 & cie) Image.verify() levant DecompressionBombError.
Path traversal sur browse / image / delete Validation explicite + résolution + check is_relative_to.
Exfiltration via browse /etc ou /root PICARONES_BROWSE_ROOTS restreint, défaut public limité à uploads.
XSS via paramètres URL CSP default-src 'self', frame-ancestors 'none'.
Clickjacking X-Frame-Options: DENY.

Reporting de vulnérabilités

Les vulnérabilités potentielles peuvent être ouvertes via une Security Advisory GitHub (privée par défaut) sur github.com/maribakulj/Picarones.

Merci de ne pas divulguer publiquement avant qu'un correctif ne soit disponible. Les contributeurs prendront en charge la triage en moins de 14 jours.