Audit de sécurité LLM #110

Closed
opened 2026-07-07 18:36:10 +02:00 by ColinMaudry · 1 comment
ColinMaudry commented 2026-07-07 18:36:10 +02:00 (Migrated from github.com)
No description provided.
ColinMaudry commented 2026-07-07 18:45:41 +02:00 (Migrated from github.com)

🔒 Correctifs de sécurité appliqués (audit accès SQLite / prise de contrôle serveur)

Suite à l'audit, application des correctifs critiques et moyen. Aucun chemin de prise de contrôle serveur (RCE) n'a été trouvé (pas de eval/exec/subprocess/pickle, DuckDB en read_only, requêtes API à colonnes whitelistées + binding ?, sandbox systemd solide). Le risque réel portait sur la lecture/écriture de la base SQLite utilisateurs.

🔴 Critique — Contournement d'autorisation du panneau admin

src/pages/admin/liste.py — le callback _update_table (endpoint serveur global /_dash-update-component) ne revérifiait pas is_admin() : la garde n'existait que dans layout(). Un non-admin — voire un anonyme (le chemin lecture ne touche pas current_user) — pouvait :

  • lire toutes les lignes de users, subscriptions, subscriber_state (emails, siret, handles Frisbii, statuts…) ;
  • écrire n'importe quelle cellule éditable (UPDATE paramétré), y compris se hisser admin en changeant son email vers ADMIN_EMAIL.

Aggravé par l'exemption CSRF des routes /_dash (src/app.py) et le Access-Control-Allow-Origin * de nginx (exfiltration cross-origin en drive-by).

Fix : garde if not is_admin(): raise PreventUpdate en tête du callback (couvre lecture ET écriture).
Test de non-régression : tests/admin/test_callback_guard.py (invoque le callback directement en non-admin) — 3 tests. Les tests E2E existants restent verts (l'admin n'est pas affecté).

🔴 Critique (aggravant) — CORS ouvert dans nginx

colibre_ynh/conf/nginx.conf — suppression du add_header Access-Control-Allow-Origin *. L'app est à base de sessions (cookies) et expose des callbacks exemptés de CSRF ; le front Dash est same-origin et n'en a pas besoin. Un location /api/ dédié (jetons Bearer, origine explicite) reste possible si besoin plus tard.

🟠 Moyen — SECRET_KEY non généré + template .env YNH obsolète

Le template ne définissait aucun secret (dont SECRET_KEY, obligatoire au démarrage). Risque : secret ajouté à la main, potentiellement faible/prévisible → forge de cookie de session → usurpation de compte.
Fix (colibre_ynh/) :

  • scripts/install : génération d'un secret_key aléatoire (ynh_string_random --length=48) persisté comme setting ;
  • scripts/upgrade : génération d'un défaut si absent (installs existantes) ;
  • conf/.env : template complété (aligné sur .template.env), avec SECRET_KEY=__SECRET_KEY__ et secrets externes en placeholders vides à renseigner.

🟢 Bonus de durcissement (inclus dans la complétion du template)

USERS_DB_PATH et DUCKDB_PATH déplacés dans __DATA_DIR__ : hors de l'arborescence servie et persistant — sans ça, users.sqlite en défaut relatif (CWD = install_dir) était effacée à chaque upgrade par ynh_setup_source --full_replace. ⚠️ À vérifier sur l'instance en production : que la base actuelle pointe bien vers data_dir.


Reste à traiter (faible, non bloquant) : liaison OAuth sur email non vérifié (auth/routes.py:43), restore référençant des configs fail2ban jamais créées, source d'install sur branche dev mouvante plutôt qu'un tag.

Correctifs non commités (édités dans l'arbre de travail des deux dépôts).

## 🔒 Correctifs de sécurité appliqués (audit accès SQLite / prise de contrôle serveur) Suite à l'audit, application des correctifs **critiques** et **moyen**. Aucun chemin de prise de contrôle serveur (RCE) n'a été trouvé (pas de `eval`/`exec`/`subprocess`/`pickle`, DuckDB en `read_only`, requêtes API à colonnes whitelistées + binding `?`, sandbox systemd solide). Le risque réel portait sur la **lecture/écriture de la base SQLite utilisateurs**. ### 🔴 Critique — Contournement d'autorisation du panneau admin **`src/pages/admin/liste.py`** — le callback `_update_table` (endpoint serveur global `/_dash-update-component`) ne revérifiait **pas** `is_admin()` : la garde n'existait que dans `layout()`. Un non-admin — voire un **anonyme** (le chemin lecture ne touche pas `current_user`) — pouvait : - lire **toutes** les lignes de `users`, `subscriptions`, `subscriber_state` (emails, siret, handles Frisbii, statuts…) ; - écrire n'importe quelle cellule éditable (`UPDATE` paramétré), y compris se hisser admin en changeant son email vers `ADMIN_EMAIL`. Aggravé par l'exemption CSRF des routes `/_dash` (`src/app.py`) et le `Access-Control-Allow-Origin *` de nginx (exfiltration cross-origin en drive-by). **Fix** : garde `if not is_admin(): raise PreventUpdate` en tête du callback (couvre lecture ET écriture). **Test de non-régression** : `tests/admin/test_callback_guard.py` (invoque le callback directement en non-admin) — ✅ 3 tests. Les tests E2E existants restent verts (l'admin n'est pas affecté). ### 🔴 Critique (aggravant) — CORS ouvert dans nginx **`colibre_ynh/conf/nginx.conf`** — suppression du `add_header Access-Control-Allow-Origin *`. L'app est à base de sessions (cookies) et expose des callbacks exemptés de CSRF ; le front Dash est same-origin et n'en a pas besoin. Un `location /api/` dédié (jetons Bearer, origine explicite) reste possible si besoin plus tard. ### 🟠 Moyen — `SECRET_KEY` non généré + template `.env` YNH obsolète Le template ne définissait aucun secret (dont `SECRET_KEY`, obligatoire au démarrage). Risque : secret ajouté à la main, potentiellement faible/prévisible → **forge de cookie de session** → usurpation de compte. **Fix** (`colibre_ynh/`) : - `scripts/install` : génération d'un `secret_key` aléatoire (`ynh_string_random --length=48`) persisté comme setting ; - `scripts/upgrade` : génération d'un défaut si absent (installs existantes) ; - `conf/.env` : template complété (aligné sur `.template.env`), avec `SECRET_KEY=__SECRET_KEY__` et secrets externes en placeholders vides à renseigner. ### 🟢 Bonus de durcissement (inclus dans la complétion du template) `USERS_DB_PATH` et `DUCKDB_PATH` déplacés dans `__DATA_DIR__` : hors de l'arborescence servie **et** persistant — sans ça, `users.sqlite` en défaut relatif (CWD = `install_dir`) était **effacée à chaque upgrade** par `ynh_setup_source --full_replace`. ⚠️ À vérifier sur l'instance en production : que la base actuelle pointe bien vers `data_dir`. --- **Reste à traiter (faible, non bloquant)** : liaison OAuth sur email non vérifié (`auth/routes.py:43`), `restore` référençant des configs fail2ban jamais créées, source d'install sur branche `dev` mouvante plutôt qu'un tag. *Correctifs non commités (édités dans l'arbre de travail des deux dépôts).*
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: colin/colibre#110