File size: 7,435 Bytes
d86e268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9d381c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# 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 :

```bash
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) :

```bash
# 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)

```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.