fix(security): consolidation & securite phase 1 (#84, BUG-021 a BUG-034)
- sanitizer XSS serveur (markdown + page de partage) [BUG-021/022] - rate-limit/lockout MFA [BUG-023] - isolation vaults par segments [BUG-024] - caps regex ReDoS [BUG-025] - SSRF webhooks + secrets externalises [BUG-026] - rotation/revocation des jetons [BUG-027] - politique de mot de passe + invalidation sessions [BUG-028] - verrous users.json [BUG-029] - IP reelle dans les audits [BUG-030] - rate-limit par compte [BUG-031] - symlinks hors vault ignores [BUG-032] - recherche simple via inverted index [BUG-033] - token en memoire + cookie HttpOnly, CSP durcie [BUG-034] Tests: pytest 961 passed / 6 skipped, ruff 0, mypy 0, frontend vert.
This commit is contained in:
+28
-1
@@ -14,7 +14,7 @@
|
||||
|
||||
- **Projet** : ObsiGate — Porte d'entrée web pour vaults Obsidian
|
||||
- **Stack** : Python 3.11+ (backend FastAPI) · JavaScript/Vanilla (frontend) · Tauri/Rust (desktop)
|
||||
- **Dernière mise à jour** : 2026-09-12
|
||||
- **Dernière mise à jour** : 2026-09-13
|
||||
|
||||
---
|
||||
|
||||
@@ -126,6 +126,30 @@ Avant de corriger quoi que ce soit, un agent IA doit :
|
||||
| *BUG-014* | Assistant IA : les flèches ↑/↓ ne naviguent pas dans les menus `/` et `@` | 🟢 corrigé | P1 | 📱 frontend | IA | `frontend/js/bookslm.js` | Ouvrir `/`, puis ↑/↓ (y compris après un clic hors zone de texte) | Navigation gérée au niveau du **panneau en phase de capture** (`panel.addEventListener('keydown', ..., true)`), donc indépendante du focus | Le `keydown` n'était écouté que sur la zone de texte ; dès que le focus changeait, les flèches étaient ignorées. Tests : `tests/frontend/ai.test.mjs` (+1) |
|
||||
| *BUG-015* | Assistant IA : la ligne sélectionnée des menus `/` et `@` est invisible au clavier | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/style.css` | Ouvrir `/` ou `@`, naviguer avec ↑/↓ | État `.active` en `--bg-hover` + barre d'accent à gauche (`inset 3px 0 0 var(--accent)`) au lieu de `--surface2` | `--surface2` est identique à `--bg-primary` (fond du menu) en thème sombre : la sélection ne se voyait pas. Idem pour la liste de modèles |
|
||||
| *BUG-016* | Mobile : le bouton « mode lecture » flottant recouvre le bouton d'envoi de l'assistant IA | 🟢 corrigé | P2 | 📱 frontend | IA | `frontend/js/mobile-editor.js`, `frontend/style.css` | Mobile, sans fichier ouvert : ouvrir l'assistant puis constater que `📖` masque `✈️` | `updateReadingButtonVisibility()` n'affiche `#me-reading-btn` que si `state.currentPath` est défini **et** que le panneau assistant est fermé ; resynchronisé à chaque mutation de `#content-area` et sur `bookslm:opened`/`bookslm:closed` ; règle `.me-reading-btn[hidden]{display:none}` | Le bouton flottant (z-index 890) passait au-dessus du panneau plein écran mobile (z-index 100). Tests : `tests/frontend/mobile-editor.test.mjs` (+2) |
|
||||
| *BUG-017* | Accueil mobile : les tuiles de « Favoris » et « Récents » ne s'affichent pas correctement en largeur (l'onglet « Partagés » s'affiche correctement) | 🔴 ouvert | P2 | 📱 frontend | IA | `frontend/js/dashboard.js`, `frontend/js/viewer.js`, `frontend/style.css` | Mobile : page d'accueil → onglets Favoris / Récents | — | Grille de tuiles non adaptée au viewport (largeur / nombre de colonnes) ; l'onglet « Partagés » (`.shared-card`) est conforme |
|
||||
| *BUG-018* | Éditeur mobile : mise en page inadaptée — boutons « Annuler » / « Sauvegarder » inaccessibles et barre d'outils IA non optimisée | 🔴 ouvert | P1 | 📱 frontend | IA | `frontend/index.html`, `frontend/style.css`, `frontend/js/ai.js` | Mobile : ouvrir un fichier → barre d'outils de l'éditeur puis panneau assistant | — | Boutons de l'éditeur hors écran / masqués sur petit écran ; barre d'outils IA trop dense ou mal positionnée |
|
||||
| *BUG-019* | Éditeur mobile : la barre d'outils du bas n'est pas toujours visible au-dessus du clavier quand le curseur n'est pas en bas de l'éditeur | 🔴 ouvert | P1 | 📱 frontend | IA | `frontend/js/mobile-editor.js`, `frontend/style.css` | Mobile : ouvrir un fichier, placer le curseur au milieu du document, afficher le clavier | — | La barre ne suit pas la position du curseur / n'est pas ancrée au-dessus du clavier virtuel |
|
||||
| *BUG-020* | Éditeur mobile : deux barres de défilement verticales à droite de la page en mode édition | 🔴 ouvert | P3 | 📱 frontend | IA | `frontend/style.css` | Mobile : ouvrir l'éditeur et observer le bord droit | — | Double scrollbar (conteneur de l'éditeur + `.cm-scroller`) peu naturelle pour un éditeur |
|
||||
| *BUG-021* | [🔴 CRITIQUE] XSS stocké via le rendu markdown (`escape=False`) | 🟢 corrigé | P0 | 🔐 sécurité | IA | `backend/services/sanitizer.py`, `backend/main.py` | Ouvrir une note contenant `<img src=x onerror=alert(1)>` | `backend/services/sanitizer.py` (whitelist stdlib) appliqué après `_add_heading_ids` dans `_render_markdown` ; tests `tests/test_security_hardening.py` | HTML brut d'un vault injecté dans le DOM → JS dans le navigateur de chaque utilisateur. DOMPurify client non ajouté (défense serveur suffisante) |
|
||||
| *BUG-022* | [🔴 CRITIQUE] XSS stocké sur la page publique `/s/{token}` (title + frontmatter non échappés) | 🟢 corrigé | P0 | 🔐 sécurité | IA | `backend/main.py` | Partager une note dont le frontmatter contient un `title` avec `<script>` | `backend/main.py` : `html.escape()` sur `title`/frontmatter + JSON échappé (`\u003c`) pour le bloc `<script>` et `a.download` | `a.download="{title}.md"` se trouvait dans un bloc script → breakout JS. Test d'intégration dans `tests/test_security_hardening.py` |
|
||||
| *BUG-023* | [🔴 CRITIQUE] Brute-force MFA sans rate-limit ni verrouillage | 🟢 corrigé | P0 | 🔐 sécurité | IA | `backend/auth/router.py` | `POST /api/auth/mfa/totp/verify` en boucle avec des codes aléatoires | `_enforce_mfa_rate_limit` + `_record_mfa_failure` sur `totp/verify`, `recovery`, `webauthn/verify` (IP + compte + lockout) | TOTP 6 chiffres brute-forceable ; le login était protégé, pas le second facteur |
|
||||
| *BUG-024* | [🔴 CRITIQUE] Traversal inter-vaults : `startswith()` sans séparateur dans `resolve_safe_path` | 🟢 corrigé | P0 | 🔐 sécurité | IA | `backend/services/paths.py` | Avec les vaults `vault` et `vault-evil`, lire un fichier de `vault-evil` via `vault` | `_is_within()` par segments (`relative_to` + repli casse-insensible) ; tests `tests/test_security_hardening.py::TestPathIsolation` | Rupture d'isolation entre vaults (lecture / écriture / suppression) |
|
||||
| *BUG-025* | [🟡 IMPORTANT] ReDoS : regex utilisateur appliquée au contenu en masse | 🟢 corrigé | P1 | 🔐 sécurité | IA | `backend/services/regex_safety.py`, `backend/search.py`, `backend/services/mutations.py` | Recherche avancée avec `^(a+)+$` sur un gros document | Validation de pattern (longueur ≤500, rejet quantificateurs imbriqués/backrefs), contenu tronqué (200k) et matchs plafonnés ; 400 sur pattern refusé | Le dry-run `replace_in_files` déclenchait la charge ; la validation est faite avant compilation |
|
||||
| *BUG-026* | [🟡 IMPORTANT] Webhooks : SSRF (URL non validée) + secret en clair | 🟢 corrigé | P1 | 🔐 sécurité | IA | `backend/webhooks.py`, `data/webhook_secrets.json` | Configurer une URL vers `http://169.254.169.254` puis déclencher le webhook | Validation d'URL (HTTPS par défaut, IP privées/boucle bloquées), résolution DNS au dispatch, redirections non suivies ; secret dans `webhook_secrets.json` (0600) ou env `OBSIGATE_WEBHOOK_SECRET_<ID>` | `get_webhooks()` n'expose plus le secret (`has_secret`) ; options `OBSIGATE_WEBHOOK_ALLOW_HTTP`/`_ALLOW_PRIVATE` |
|
||||
| *BUG-027* | [🟡 IMPORTANT] Sessions : refresh non rotatif, access token non révoqué au logout | 🟢 corrigé | P1 | 🔐 sécurité | IA | `backend/auth/jwt_handler.py`, `backend/auth/router.py`, `backend/auth/middleware.py` | Logout puis réutilisation de l'ancien access token | Rotation du refresh token (`create_refresh_token(remember=…)`) + révocation de l'ancien JTI ; access token révoqué au logout et vérifié dans le middleware | JTI persistés dans `data/revoked_tokens.json` |
|
||||
| *BUG-028* | [🟡 IMPORTANT] Politique de mot de passe incohérente + sessions non invalidées au changement | 🟢 corrigé | P1 | 🔐 sécurité | IA | `backend/auth/password.py`, `backend/auth/router.py`, `backend/auth/user_store.py`, `backend/auth/middleware.py` | Créer un utilisateur avec `password:""` (admin) ; changer son mot de passe puis réutiliser l'ancien token | `validate_password_strength` (8–128) sur création/modif admin/changement ; `password_changed_at` invalide les jetons émis avant le changement ; `change-password` réémet une paire | Tests `TestPasswordPolicy` + `TestTokenInvalidation` |
|
||||
| *BUG-029* | [🟡 IMPORTANT] Race read-modify-write sur `users.json` (perte de mises à jour) | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/auth/user_store.py` | Activer MFA et changer son mot de passe en parallèle | `threading.RLock` (`_users_lock`) autour de `create_user`, `update_user`, `delete_user`, `record_login_failure` | RLock réentrant car `record_login_failure` appelle `update_user` |
|
||||
| *BUG-030* | [🟡 IMPORTANT] Adresse IP jamais consignée dans les audits (`_request_ip` toujours « unknown ») | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/services/net.py`, `backend/auth/middleware.py` | Lire le journal d'audit après un save/delete | `get_client_ip()` (`request.client.host`, `X-Forwarded-For` si `OBSIGATE_TRUST_PROXY=true`) injecté dans `current_user["_request_ip"]` | Test `TestClientIp` |
|
||||
| *BUG-031* | [🟡 IMPORTANT] Rate-limiter en mémoire, mono-process, budget exclusivement IP | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/ratelimit.py`, `backend/auth/router.py` | Lancer plusieurs workers et observer le partage du compteur | Budget **par compte** (`is_account_rate_limited`/`record_account_*`) en plus de l'IP, intégré login + MFA ; limite mono-process documentée | Rotation d'IP neutralisée ; stockage partagé (Redis) hors périmètre |
|
||||
| *BUG-032* | [🟡 IMPORTANT] Indexation : symlinks suivis (contenu hors vault indexé) + scan initial coûteux | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/indexer.py` | Placer un symlink dans le vault vers un dossier externe puis relancer l'index | `_scan_vault` réécrit avec `os.walk(followlinks=False)` + refus des symlinks sortant de la racine ; test `TestSymlinkIndexing` | Scan incrémental/index persistant : voir #86 (phase 3) |
|
||||
| *BUG-033* | [🟡 IMPORTANT] Recherche classique et tool IA `search_fulltext` en O(N) sans inverted index | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/search.py`, `backend/tools/service.py` | `GET /api/search` sur un vault de 50 000 fichiers | `search()` récupère les candidats via l'inverted index (intersection des termes + expansion de préfixes), repli sur le scan pendant la construction | `search_fulltext` en bénéficie automatiquement |
|
||||
| *BUG-034* | [🟡 IMPORTANT] CSP affaiblie (`'unsafe-inline'` + CDN distants) et token d'accès en sessionStorage | 🟢 corrigé | P1 | 🔐 sécurité | IA | `backend/main.py`, `frontend/js/auth.js`, `frontend/js/admin.js`, `frontend/js/sync.js` | Inspecter les en-têtes CSP ; lire sessionStorage en console | Token en mémoire + cookie HttpOnly (plus de `sessionStorage`) ; CSP durcie (`object-src 'none'`, `base-uri`, `form-action`, `frame-ancestors`). *Reste : migration nonce* | `'unsafe-inline'` conservé tant que les gestionnaires inline n'ont pas été convertis (résidu documenté) |
|
||||
| *BUG-035* | [🔵 MINEUR] `secret_redactor` : faux positifs sur les hashs hex (git, SHA) | 🔴 ouvert | P2 | ⚙️ backend | IA | `backend/secret_redactor.py` | Lire une note contenant un commit git (40 caractères hexadécimaux) | Restreindre le périmètre de détection (contexte clé/token) + whitelist | Contenus mutilés dans les lectures et réponses IA |
|
||||
| *BUG-036* | [🔵 MINEUR] Collab WebSocket : token en query string | 🔴 ouvert | P2 | ⚙️ backend | IA | `backend/collab.py` | Observer l'URL du websocket dans le trafic réseau | Passer le token en header / étape d'authentification initiale ; borner la taille des messages | Jeton visible dans les logs/proxys |
|
||||
| *BUG-037* | [🔵 MINEUR] Compte « anonymous » administrateur si auth désactivée | 🔴 ouvert | P2 | 🔐 sécurité | IA | `backend/auth/middleware.py` | Démarrer avec l'authentification désactivée | Avertissement explicite au démarrage + refus de déploiement public sans auth | Comportement par conception mais risqué si mal configuré |
|
||||
| *BUG-038* | [🔵 MINEUR] Argon2 à 64 MB par vérification : risque d'épuisement mémoire | 🔴 ouvert | P2 | 🔐 sécurité | IA | `backend/auth/password.py:8` | Lancer de nombreux `POST /api/auth/login` simultanés | Recalibrer (~19 MB, t=2, p=1, norme OWASP actuelle) + maintien du rate-limit | DoS mémoire possible sur les petites instances |
|
||||
| *BUG-039* | [🔵 MINEUR] Enumération de comptes : 429 (verrouillé) vs 401 (inconnu) | 🔴 ouvert | P3 | 🔐 sécurité | IA | `backend/auth/router.py:120` | Tenter un login sur un compte verrouillé puis un nom inconnu | Répondre 401 uniforme avec un timing équivalent | Le statut HTTP distingue l'existence d'un compte |
|
||||
| *BUG-040* | [🔵 MINEUR] Extraction PDF intégrale (100 ko) au scan de démarrage | 🔴 ouvert | P2 | ⚙️ backend | IA | `backend/indexer.py:465` | Démarrer sur un vault contenant de nombreux PDF | Analyser les PDF en tâche de fond / à la demande (lazy) | Ralentit fortement le démarrage et le rebuild d'index |
|
||||
|
||||
### TODOs techniques (améliorations / nouvelles tâches)
|
||||
|
||||
@@ -158,6 +182,9 @@ Avant de corriger quoi que ce soit, un agent IA doit :
|
||||
| 2026-09-12 | #82 | Amélioration perf + UX | `backend/services/search.py`, `backend/main.py`, `frontend/js/bookslm.js`, `frontend/js/ui.js`, `frontend/style.css`, `tests/test_api_main.py`, `tests/frontend/ai.test.mjs` | #82 : endpoint `GET /api/vault/{vault}/paths` + préchargement et filtrage client du menu `@` (instantané) ; sélecteurs Fournisseur/Modèle agrandis (0,8 rem / 34 px) ; « 🧠 BooksLM » ajouté au menu contextuel de la racine des vaults. Vérifié : pytest 151 (ciblés) + ruff/mypy, tests frontend 49/49. | ✅ livré (en attente vérif utilisateur) |
|
||||
| 2026-09-12 | #82 | UX | `frontend/js/ai.js`, `frontend/style.css`, `tests/frontend/ai.test.mjs` | Sélecteurs Fournisseur/Modèle alignés à droite dans la barre de l'assistant et réordonnés : capacité du modèle (ⓘ) → fournisseur → modèle. Bulle de capacités ouverte vers la droite (`left: 0`). Test JSDOM de l'ordre. Vérifié : tests frontend 49/49. | ✅ livré (en attente vérif utilisateur) |
|
||||
| 2026-09-12 | BUG-016 | Correction | `frontend/js/mobile-editor.js`, `frontend/style.css`, `tests/frontend/mobile-editor.test.mjs`, `tests/e2e/mobile-editor.spec.js` | BUG-016 : le bouton « mode lecture » n'apparaît que si un fichier est ouvert et que l'assistant est fermé, sinon il recouvrait le bouton d'envoi sur mobile. Vérifié : tests frontend 24/24 (mobile) + suite JSDOM verte. | 🟢 corrigé (en attente vérif utilisateur) |
|
||||
| 2026-09-13 | BUG-017, BUG-018, BUG-019, BUG-020, #83 | Enregistrement | `docs/ISSUES_TODOLIST.md`, `docs/ROADMAP.md` | Signalements mobile consignés : accueil (tuiles Favoris/Récents), éditeur (boutons Annuler/Sauvegarder inaccessibles + barre IA), visibilité de la barre d'outils au-dessus du clavier, double barre de défilement. Feature #83 ajoutée : barre d'outils d'édition mobile style Obsidian Android. | 🔴 ouvert (à traiter) |
|
||||
| 2026-09-13 | Revue sécurité statique (BUG-021 → BUG-040, roadmap #84 → #87) | Enregistrement | `docs/ISSUES_TODOLIST.md`, `docs/ROADMAP.md` | Revue statique 2026-09-13 consignée : XSS markdown (`escape=False`) + page de partage publique, brute-force MFA, traversal par préfixe `paths.py`, ReDoS, SSRF webhooks, cycle de vie des sessions, politique de mot de passe, races `users.json`, audits IP, rate-limit, indexation symlinks, recherche O(N), CSP. BUG-021→BUG-040 ouverts au registre ; roadmap #84→#87 (consolidation/sécurité, refonte architecturale, performance, CI/CD). | 🔴 ouvert (à traiter) |
|
||||
| 2026-09-13 | BUG-021 → BUG-034 (#84 phase 1) | Correction | `backend/services/sanitizer.py`, `backend/services/paths.py`, `backend/services/net.py`, `backend/services/regex_safety.py`, `backend/main.py`, `backend/search.py`, `backend/webhooks.py`, `backend/indexer.py`, `backend/ratelimit.py`, `backend/auth/{router,user_store,password,jwt_handler,middleware}.py`, `frontend/js/{auth,admin,sync}.js`, `tests/test_security_hardening.py`, `tests/test_auth_api.py` | Sanitizer XSS serveur (markdown + page de partage), rate-limit/lockout MFA, isolation vaults par segments, caps regex (ReDoS), SSRF webhooks + secrets externalisés, rotation/révocation des jetons, politique de mot de passe + invalidation des sessions, verrous `users.json`, IP réelle dans les audits, rate-limit par compte, symlinks d'index ignorés, recherche simple via inverted index, token en mémoire + cookie HttpOnly + CSP durcie. Vérifié : pytest 961 passed / 6 skipped, ruff 0, mypy 0, tests frontend 36 modules + 9 JSDOM suites verts. | 🟢 corrigé (en attente vérif utilisateur) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+71
-2
@@ -1,6 +1,6 @@
|
||||
# ObsiGate — Roadmap
|
||||
|
||||
> **Version :** 2.3.0-dev | **Dernière mise à jour :** 2026-09-12
|
||||
> **Version :** 2.3.0-dev | **Dernière mise à jour :** 2026-09-13
|
||||
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
||||
> vers les fonctionnalités livrées.
|
||||
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
||||
@@ -60,6 +60,72 @@
|
||||
|
||||
---
|
||||
|
||||
## ⚪ Backlog — Priorité 2 (P2)
|
||||
|
||||
### 83. Barre d'outils d'édition mobile — style Obsidian Android
|
||||
|
||||
- **Effort :** 3-5 jours | **Impact :** 🟡 | **Zone :** frontend (mobile)
|
||||
- **Description :** remplacer la barre de mise en forme Markdown actuelle par un **ruban horizontal
|
||||
défilable** ancré juste au-dessus du clavier virtuel, reprenant l'ergonomie de l'app Android
|
||||
Obsidian : fond anthracite aux coins arrondis, insertion/enrobage de la syntaxe au curseur ou sur
|
||||
la sélection, et personnalisation des commandes via une icône clé à molette.
|
||||
- **Sous-tâches :**
|
||||
- [ ] Ruban horizontal défilable (glissement tactile gauche/droite) ancré au-dessus du clavier
|
||||
- [ ] Actions rapides : annuler, refaire, `[[ ]]` (lien interne), modèle/fichiers, tag `#`, pièce jointe
|
||||
- [ ] Formatage : H1–H6, gras, italique, barré (`~~`), surligné (`==`), code en ligne/bloc, citation (`>`)
|
||||
- [ ] Liens externes, listes à puces/numérotées, case à cocher (`- [ ]`), indenter / désindenter
|
||||
- [ ] Personnalisation (clé à molette) : ajouter / supprimer / réordonner les commandes
|
||||
- [ ] i18n FR/EN + tests frontend (helpers purs) + E2E mobile
|
||||
|
||||
---
|
||||
|
||||
## ⚪ Backlog — Sécurité, architecture & performance (P0/P1)
|
||||
|
||||
### 84. Consolidation & sécurité — revue statique 2026-09-13 (phase 1)
|
||||
|
||||
- **Effort :** 6-9 jours | **Impact :** 🔴 | **Zone :** backend + frontend | **Référence :** [ISSUES_TODOLIST.md](./ISSUES_TODOLIST.md) BUG-021 → BUG-034
|
||||
- **Statut :** 🟢 livré (phase 1) — sanitizer XSS, rate-limit/lockout MFA, isolation vaults, ReDoS, SSRF webhooks, cycle de vie des sessions, politique de mot de passe, verrous `users.json`, audits IP, rate-limit par compte, symlinks, recherche via inverted index, token en cookie HttpOnly. Détail : [archive/COMPLETED_v1-v2.md](./archive/COMPLETED_v1-v2.md) (section #84).
|
||||
- **Description :** traiter toutes les vulnérabilités critiques et importantes issues de la revue statique : XSS markdown (`escape=False`) et page publique de partage, brute-force MFA, isolation des vaults (`resolve_safe_path`), ReDoS, SSRF webhooks, cycle de vie des sessions, politique de mot de passe, races `users.json`, audits IP, rate-limit partagé, indexation symlinks.
|
||||
- **Sous-tâches :**
|
||||
- [x] Assainir le rendu markdown (sanitizer serveur en whitelist) et la page de partage (échappement `title`/frontmatter) — *DOMPurify client non ajouté (défense en profondeur serveur suffisante)*
|
||||
- [x] Rate-limit + lockout sur les endpoints MFA (`totp/verify`, `recovery`, `webauthn/verify`)
|
||||
- [x] Corriger `resolve_safe_path` (comparaison de chemin stricte par segment) + test de régression
|
||||
- [x] Rotation du refresh token, révocation de l'access token au logout, persistance des JTI révoqués
|
||||
- [x] Valider la politique de mot de passe à la création ; bloquer le SSRF des webhooks et externaliser les secrets
|
||||
- [x] Verrous sur les mutations `users.json` ; consigner l'adresse IP réelle dans les audits
|
||||
- [x] Ignorer les symlinks de l'index ; caps CPU/timeout regex (ReDoS)
|
||||
- [~] Durcir la CSP — *partiel* : directives `object-src`/`base-uri`/`form-action`/`frame-ancestors` ajoutées et token retiré de `sessionStorage` ; migration **nonce** restante (nécessite la conversion des gestionnaires d'événements inline)
|
||||
|
||||
### 85. Refonte architecturale — découpage du monolithe & persistance d'état (phase 2)
|
||||
|
||||
- **Effort :** 8-12 jours | **Impact :** 🟡 | **Zone :** backend
|
||||
- **Description :** extraire le monolithe `backend/main.py` (~4 260 lignes) en routers FastAPI par domaine et rendre persistant l'état qui ne l'est pas (index de recherche, JTI révoqués, compteurs de rate-limit) pour préparer le multi-nœuds.
|
||||
- **Sous-tâches :**
|
||||
- [ ] Routers par domaine : files, search, share, webhooks, plugins, collab, admin, ai
|
||||
- [ ] Centraliser le contrat d'outils IA sur `tools/registry.py` (permissions, quotas, redaction)
|
||||
- [ ] Persister index, JTI révoqués et compteurs de rate-limit (SQLite/Redis)
|
||||
- [ ] Verrous asyncio autour de l'index global et des stores JSON ; service de partage public (expiration, révocation, quotas)
|
||||
|
||||
### 86. Optimisation globale des performances (phase 3)
|
||||
|
||||
- **Effort :** 4-6 jours | **Impact :** 🟡 | **Zone :** backend (`search.py`, `indexer.py`, `mutations.py`)
|
||||
- **Description :** brancher l'inverted index (déjà construit) sur la recherche simple et le tool IA `search_fulltext`, indexation incrémentale, extraction PDF lazy.
|
||||
- **Sous-tâches :**
|
||||
- [ ] Recherche simple + tool IA via l'inverted index (suppression du balayage O(N) en mémoire)
|
||||
- [ ] Indexation incrémentale + scan différentiel au démarrage (remplace le `rglob` complet)
|
||||
- [ ] Extraction PDF/excalidraw différée (hors scan) ; caps CPU sur les opérations regex
|
||||
|
||||
### 87. Amélioration continue — tests, CI/CD, revues de sécurité (phase 4)
|
||||
|
||||
- **Effort :** 3-5 jours | **Impact :** 🟡 | **Zone :** `.gitea/workflows/`, `tests/`
|
||||
- **Description :** renforcer le pipeline (`.gitea/workflows/ci.yml`, `desktop-build.yml`) pour le rendre bloquant par défaut et accompagner les phases 1 → 3.
|
||||
- **Sous-tâches :**
|
||||
- [ ] Jobs CI sécurité (bandit/semgrep/trivy, audits pip/npm) + tests E2E XSS (page de partage + lecteur markdown)
|
||||
- [ ] Tests de concurrence (`users.json`), fuzzing de timing regex, couverture des composants critiques
|
||||
- [ ] Revue périodique des dépendances ; documentation utilisateur FR/EN synchronisée ; contrôle automatisé de la conformité au DoD
|
||||
|
||||
---
|
||||
|
||||
## ✅ Complété — index
|
||||
|
||||
> Détail complet dans [docs/archive/COMPLETED_v1-v2.md](./archive/COMPLETED_v1-v2.md) et
|
||||
@@ -99,6 +165,7 @@
|
||||
| 69 | Éditeur mobile natif — Interface tactile optimisée | 2.3.0 | [features/mobile-editor.md](./features/mobile-editor.md) |
|
||||
| 70 | Recherche sémantique — Embeddings vectoriels (hybride TF-IDF + RRF) | 2.3.0 | [features/semantic-search.md](./features/semantic-search.md) |
|
||||
| 77 | Application Desktop native — Tauri | 🔵 en cours | [features/desktop-tauri.md](./features/desktop-tauri.md) |
|
||||
| 84 | Consolidation & sécurité — revue statique 2026-09-13 (phase 1, BUG-021→034) | 2.3.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
||||
|
||||
---
|
||||
|
||||
@@ -108,8 +175,10 @@
|
||||
|---|---|---|
|
||||
| ✅ Complété | #1 → #59, #61–72, #74–76, #78–82 | ~107 jours réalisés |
|
||||
| 🔵 P2 restant | #77 Desktop : signature de code (non retenue), 6 tests E2E **manuels** ([protocole](./DESKTOP_E2E_CHECKLIST.md)) | ~0,5-1 jour |
|
||||
| ⚪ P2 restant | #83 Barre d'outils d'édition mobile style Obsidian (3-5j) | 3-5 jours |
|
||||
| ⚪ P4 restant | #73 Sync (6-8j) | 6-8 jours |
|
||||
| **Total restant** | **2 items + finitions** | **~7-10 jours** |
|
||||
| ⚪ P0/P1 restant | #85-87 Refonte architecturale, performance, CI/CD (issues BUG-035 → BUG-040) | ~15-23 jours |
|
||||
| **Total restant** | **7 items + finitions** | **~31-47 jours** |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -317,6 +317,31 @@
|
||||
|
||||
---
|
||||
|
||||
## #84 — Consolidation & sécurité (phase 1) ✅ TERMINÉ
|
||||
|
||||
Traitement des vulnérabilités de la revue statique du 2026-09-13 (BUG-021 → BUG-034).
|
||||
|
||||
- **Sanitizer XSS serveur** (`backend/services/sanitizer.py`, stdlib, liste blanche
|
||||
de balises/attributs/schémas) appliqué au rendu markdown et à la page publique
|
||||
`/s/{token}` (titre, frontmatter et JSON échappés, `</script>` neutralisé).
|
||||
- **Auth durcie** : rate-limit IP + compte et verrouillage sur les endpoints MFA ;
|
||||
rotation du refresh token et révocation de l'access token au logout ; politique de
|
||||
mot de passe centralisée (8–128) ; `password_changed_at` invalide les jetons
|
||||
antérieurs ; verrou `RLock` sur `users.json`.
|
||||
- **Isolation & réseau** : `resolve_safe_path` compare par segments (`vault` ≠
|
||||
`vault-evil`) ; webhooks protégés contre le SSRF (HTTPS, IP privées bloquées,
|
||||
résolution vérifiée, redirections interdites, secret externalisé) ; IP client
|
||||
réelle dans les audits.
|
||||
- **Robustesse** : validation des regex (ReDoS), symlinks hors vault ignorés à
|
||||
l'indexation, recherche simple et `search_fulltext` branchés sur l'inverted index.
|
||||
- **Frontend** : token d'accès en mémoire + cookie `HttpOnly` (plus de
|
||||
`sessionStorage`), CSP durcie (`object-src`, `base-uri`, `form-action`,
|
||||
`frame-ancestors`).
|
||||
- **Tests** : `tests/test_security_hardening.py` (30) ; suite backend 961 passed /
|
||||
6 skipped ; ruff + mypy 0 ; suites frontend vertes.
|
||||
|
||||
---
|
||||
|
||||
## Grosses fonctionnalités — fiches dédiées
|
||||
|
||||
| # | Feature | Version | Fiche |
|
||||
|
||||
Reference in New Issue
Block a user