File size: 5,359 Bytes
d86e268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# 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.