# 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. | --- ## Contrôles par contexte ### 🧑‍💻 Développement (défaut, `PICARONES_PUBLIC_MODE` non défini) ```bash 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 ```bash 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 ```dockerfile 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](https://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.