fix(security): consolidation & securite phase 1 (#84, BUG-021 a BUG-034)
CI / lint (push) Successful in 1m20s
CI / security (push) Successful in 47s
CI / test (push) Successful in 2m21s
CI / build (push) Successful in 43s
CI / e2e (push) Successful in 10m48s

- 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:
2026-09-13 10:51:42 -04:00
parent d47fcbf2be
commit 162a5b4acc
29 changed files with 1690 additions and 281 deletions
+40
View File
@@ -12,6 +12,46 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Sécurité
- **#84 Consolidation & sécurité — phase 1 (BUG-021 → BUG-034)** — traitement des
vulnérabilités de la revue statique du 2026-09-13 :
- **BUG-021/022 — XSS stocké** : nouveau sanitizer serveur en liste blanche
(`backend/services/sanitizer.py`, stdlib) appliqué au rendu markdown ; échappement
systématique du `title`, du frontmatter et du JSON de la page publique `/s/{token}`
(`</script>` neutralisé). Tests : `tests/test_security_hardening.py` (9).
- **BUG-023 — brute-force MFA** : rate-limit IP + compte et verrouillage de compte sur
`mfa/totp/verify`, `mfa/recovery` et `mfa/webauthn/verify` (`_enforce_mfa_rate_limit`).
- **BUG-024 — traversal inter-vaults** : `resolve_safe_path` compare désormais les chemins
par **segment** (`Path.relative_to` + repli casse-insensible), plus par préfixe de chaîne :
`vault` ne peut plus lire `vault-evil`.
- **BUG-025 — ReDoS** : validation des regex utilisateur (longueur max, rejet des
quantificateurs imbriqués/backrefs), contenu tronqué et nombre de matchs plafonné
(`backend/services/regex_safety.py`), appliqué à la recherche avancée et au find/replace.
- **BUG-026 — SSRF webhooks** : validation d'URL (HTTPS par défaut, IP privées/boucle
interdites, résolution DNS vérifiée au dispatch, redirections non suivies) et
**externalisation du secret** dans `data/webhook_secrets.json` (0600) ou variable
`OBSIGATE_WEBHOOK_SECRET_<ID>` (plus de secret en clair dans `webhooks.json`).
- **BUG-027 — sessions** : rotation du refresh token à chaque usage, révocation du JTI de
l'access token au logout, et vérification de la révocation dans le middleware.
- **BUG-028 — politique de mot de passe** : validation centralisée (8–128 caractères) à la
création, à la modification admin et au changement ; `password_changed_at` invalide tous
les jetons émis avant un changement de mot de passe.
- **BUG-029 — race `users.json`** : verrou `threading.RLock` autour des cycles
lecture-modification-écriture.
- **BUG-030 — audits** : l'adresse IP réelle du client (`X-Forwarded-For` si
`OBSIGATE_TRUST_PROXY=true`) est injectée dans `current_user` et consignée dans les audits.
- **BUG-031 — rate-limiter** : budget **par compte** en plus du budget par IP (rotation d'IP
neutralisée) ; limite mono-process documentée.
- **BUG-032 — indexation** : `_scan_vault` utilise `os.walk(followlinks=False)` et refuse
tout symlink sortant de la racine du vault.
- **BUG-033 — recherche O(N)** : la recherche simple et l'outil IA `search_fulltext`
utilisent l'inverted index (repli sur le scan pendant la construction).
- **BUG-034 — CSP & jetons** : le token d'accès n'est plus persisté dans `sessionStorage`
(mémoire + cookie `HttpOnly`) ; directives CSP durcies (`object-src`, `base-uri`,
`form-action`, `frame-ancestors`). *Reste : migration CSP par nonce (exige la conversion
des gestionnaires d'événements inline).*
### Ajouté
- **#82 Assistant IA — menu `@` instantané** — nouvel endpoint `GET /api/vault/{vault}/paths`