ObsiGate/docs/AUDIT_TECHNIQUE_2026-05-27.md
Bruno Charest 58a0ffc76c feat: quick wins - dockerignore, env secrets, gzip, cache-control
- Add .dockerignore to exclude .git, __pycache__, docs, etc. from Docker context
- Create .env.example template with documented env vars
- Move OBSIGATE_ADMIN_PASSWORD from docker-compose.yml to env_file: .env
- Add .env.* to .gitignore (excluding .env.example)
- Enable GZipMiddleware for ~70% bandwidth reduction on text responses
- Add Cache-Control: immutable for /static/ assets
- Update ROADMAP: mark all 4 quick wins as done, add audit findings
- Add comprehensive technical audit report (AUDIT_TECHNIQUE_2026-05-27.md)
2026-05-27 20:35:08 -04:00

385 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# RAPPORT D'AUDIT TECHNIQUE — OBSIGATE v1.4.0/1.5.0
**Projet :** Portail web ultra-léger pour vaults Obsidian
**Auteur :** Bruno Beloeil
**Date d'audit :** 27 mai 2026
**Auditeur :** Hermes Agent (DeepSeek V4 Pro)
**Méthodologie :** Revue de code exhaustive — 20,457 lignes analysées
---
## 1. VUE D'ENSEMBLE
### 1.1 Statistiques du codebase
| Composant | Fichier(s) | Lignes | Langage |
|-----------|-----------|--------|---------|
| Backend API | `backend/main.py` | 3,105 | Python/FastAPI |
| Moteur de recherche | `backend/search.py` | 1,211 | Python |
| Indexeur | `backend/indexer.py` | 987 | Python |
| Auth (5 modules) | `backend/auth/*.py` | ~600 | Python |
| Modules backend | 11 fichiers | ~1,200 | Python |
| Frontend SPA | `frontend/app.js` | 8,869 | Vanilla JS |
| Styles | `frontend/style.css` | 6,134 | CSS3 (variables) |
| Template HTML | `frontend/index.html` | 1,138 | HTML5 |
| **Total** | | **~20,457** | |
### 1.2 Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Navigateur (SPA vanilla JS — 8,869 lignes) │
│ • CodeMirror 6, Lucide Icons, Highlight.js │
│ • PWA (Service Worker, manifest.json) │
│ • dark/light theme via CSS variables │
└──────────────────┬──────────────────────────────────────┘
│ REST API + SSE
┌──────────────────▼──────────────────────────────────────┐
│ FastAPI (backend/main.py — 3,105 lignes) │
│ • 50+ endpoints REST │
│ • SSE Manager (Server-Sent Events) │
│ • Security middleware (CSP, X-Frame, etc.) │
│ • Auth JWT + Argon2id + rate limiting │
└──────┬───────────┬──────────┬──────────┬────────────────┘
│ │ │ │
┌────▼────┐ ┌───▼────┐ ┌───▼───┐ ┌───▼──────────┐
│ Indexer │ │ Search │ │ Auth │ │ Modules │
│(987 loc)│ │(1211) │ │(~600) │ │watcher,share │
│InMemory │ │TF-IDF │ │JWT │ │pdf,audit, │
│+ lookup │ │+ facets│ │users │ │webhooks,etc │
└─────────┘ └────────┘ └───────┘ └──────────────┘
```
---
## 2. FORCES DU PROJET
### 2.1 Architecture (Excellent)
- **Séparation propre** backend/frontend sans framework lourd
- **In-memory indexing** performant — pas de base de données externe
- **Inverted Index TF-IDF** avec mise à jour incrémentale via hooks (plan.md implémenté)
- **Watchdog hot-reload** avec debounce (2s) et SSE broadcast
- **Multi-stage Docker** (~180MB) avec utilisateur non-root (UID 1000)
- **CodeMirror 6** en tant qu'éditeur intégré — choix moderne
- **PWA complète** — manifest, service worker, icônes multi-tailles
- **Zero dépendance npm** côté frontend — vanilla JS pur
### 2.2 Sécurité (Très bon)
- **Path traversal protection** (`_resolve_safe_path`) avec résolution symlink-aware
- **JWT + Argon2id** avec refresh tokens sur cookies httpOnly
- **Rate limiting** IP-based (10 tentatives/15min) + lockout par compte
- **Secret redaction** automatique (JWT, API keys, tokens, connection strings)
- **Audit logging** (JSON lines dans `data/audit.log`) avec rotation 10MB
- **Backup automatique** avant écriture/suppression (`.obsigate-backup/`)
- **Security headers** (CSP, X-Frame-Options, XSS-Protection, Referrer-Policy)
- **Safe atomic writes** (tmp + replace) pour shares.json, webhooks.json
### 2.3 Fonctionnalités (Riche)
- **Multi-vault** avec contrôle d'accès granulaire par utilisateur
- **Recherche avancée** : opérateurs `tag:`, `vault:`, `title:`, `path:`, `ext:`, regex
- **Autocomplétion intelligente** : historique + titres + tags avec debounce 150ms
- **Wikilinks** résolus avec backlink index bidirectionnel
- **Rendu d'images** 7 stratégies de résolution (Obsidian-compatible)
- **Mode onglets** (preview/persistant) avec drag-and-drop
- **Éditeur intégré** CodeMirror 6 avec syntax highlighting (10+ langages)
- **Dashboard** avec 4 panneaux (stats, bookmarks, récents, partagés)
- **Partage public** avec tokens 64-char hex, expiration configurable
- **Export PDF** via WeasyPrint
- **Webhooks** avec signature HMAC-SHA256
- **Find-in-page** avec TreeWalker (regex, case, whole word)
- **TOC/Outline** avec scroll spy et barre de progression
- **Vault settings** persistés (hideHiddenFiles)
- **Saved searches** avec CRUD API
### 2.4 Qualité du code (Bon)
- Docstrings Google-style sur toutes les fonctions publiques backend
- Annotations de type sur tous les paramètres et retours
- Modèles Pydantic documentés avec `Field(description=...)`
- Logging structuré par module (`obsigate.indexer`, `obsigate.search`, etc.)
- Gestion d'erreurs systématique (try/catch avec toast user-friendly)
- Frontend vanilla JS — zéro framework, zéro npm
- `"use strict"` + IIFE wrapper
- Conventions de nommage cohérentes
---
## 3. PROBLÈMES ET VULNÉRABILITÉS
### 3.1 Critique (P0)
**Aucun problème critique détecté.** Les failles P0 identifiées précédemment (rate limiting, secret redaction) ont été corrigées.
### 3.2 Élevé (P1)
#### H1 — Pas de tests automatisés
Le projet n'a **aucun test** — pas de `pytest`, pas de `tests/`, pas de CI/CD. Avec 20k+ lignes de code, c'est le risque technique numéro 1. Chaque modification est un saut dans l'inconnu.
**Impact :** Régressions silencieuses, bugs non détectés, refactoring risqué, onboarding développeur difficile.
#### H2 — Pas de CI/CD pipeline
Aucun fichier `.github/workflows/` ou équivalent, pas de linting automatisé, pas de vérification de build Docker. Le `build.sh` vérifie les prérequis mais ne run aucun test.
#### H3 — Mot de passe admin dans `docker-compose.yml`
```yaml
- OBSIGATE_ADMIN_PASSWORD=chab30
```
C'est le fichier de configuration local — le risque est limité si le dépôt est privé, mais c'est une mauvaise pratique à corriger. **Recommandation :** utiliser un fichier `.env` + `env_file` dans docker-compose.
#### H4 — Pas de `.dockerignore`
Tout le dossier `.git` et les fichiers de documentation sont copiés dans le contexte Docker, augmentant inutilement la taille du contexte de build et risquant des fuites accidentelles.
### 3.3 Modéré (P2)
#### M1 — Frontend monolithique (8,869 lignes dans un seul fichier)
`app.js` est très gros pour un fichier unique. La navigation et la maintenance deviennent difficiles.
**Suggestion :** Split en modules ES (même sans build step, les `import`/`export` natifs fonctionnent dans tous les navigateurs modernes).
#### M2 — Content Security Policy permissive
La CSP actuelle autorise `'unsafe-inline'` et plusieurs CDN :
```
script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://unpkg.com https://esm.sh
```
Le risque est théorique (pas d'input utilisateur qui génère du HTML), mais `'unsafe-inline'` pourrait être retiré avec des nonces/hashes.
#### M3 — Pas de compression/streaming des réponses
Les fichiers statiques sont servis bruts. Activer la compression gzip/brotli dans Uvicorn ou via middleware FastAPI réduirait la bande passante de ~70%.
#### M4 — Index chargé entièrement en mémoire
Avec 40k+ fichiers, l'index mémoire peut devenir conséquent. Un fichier de 100KB de contenu = 100KB en RAM. 40,000 × 100KB = 4GB théoriques. La limite `SEARCH_CONTENT_LIMIT = 100_000` (100KB par fichier) atténue le problème mais un lazy-loading pourrait être nécessaire à très grande échelle.
### 3.4 Faible (P3)
#### L1 — Pas de rate limiting sur les endpoints non-auth
Les endpoints comme `/api/health` ne sont pas limités. Faible risque mais un scraper pourrait saturer le serveur.
#### L2 — Pas de validation d'extension pour les uploads
La création de fichiers via API (`POST /api/file/{vault}`) ne valide pas l'extension. Un utilisateur pourrait créer un `.php` ou `.exe` dans un vault.
#### L3 — `handle_file_move` non atomique
`remove_single_file` puis `update_single_file` — si le processus crashe entre les deux, l'index est incohérent (fichier supprimé mais pas recréé).
#### L4 — Pas d'i18n structurée
Tout le texte UI est en dur en français dans le HTML et le JS. Pour une audience internationale, l'internationalisation serait bénéfique.
#### L5 — Duplication de `IGNORED_DIRS`
Les dossiers ignorés sont définis à deux endroits : `backend/indexer.py` (ligne 69) et `backend/watcher.py` (ligne 17). Une source unique serait préférable.
---
## 4. RECOMMANDATIONS NEXT LEVEL
### 4.1 Tests et CI/CD (Impact : Très Élevé)
1. **Ajouter pytest** avec une couverture minimale de 70% sur le backend :
- Tests unitaires sur `search.py` (tokenizer, TF-IDF scoring, snippet extraction, regex search)
- Tests unitaires sur `indexer.py` (parsing frontmatter, extraction tags inline/frontmatter, wikilinks)
- Tests d'intégration sur l'API (FastAPI `TestClient`)
- Tests du rate limiter et de l'auth (JWT flow, lockout, permissions)
2. **Ajouter CI/CD** (GitHub Actions / Gitea Actions) :
```yaml
name: CI
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v1
- run: mypy backend/
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r backend/requirements.txt
- run: pytest --cov=backend --cov-report=xml
security:
runs-on: ubuntu-latest
steps:
- run: pip-audit
- run: bandit -r backend/
build:
runs-on: ubuntu-latest
steps:
- run: docker build -t obsigate:ci .
```
3. **Ajouter Playwright** pour des tests E2E sur les flows critiques (login, recherche, navigation, ouverture de fichier)
### 4.2 Performance (Impact : Élevé)
4. **Activer la compression HTTP :**
```python
from fastapi.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000)
```
5. **Ajouter des en-têtes de cache pour les assets statiques :**
```python
# Dans SecurityHeadersMiddleware ou via StaticFiles
# Cache-Control: public, max-age=31536000, immutable pour /static/
```
6. **Remplacer `bisect` par `sortedcontainers` :**
```python
from sortedcontainers import SortedList
self._sorted_tokens = SortedList() # O(log n) insert/remove au lieu de O(n)
```
7. **Pagination de l'API browse** pour les très grands dossiers (>1000 entrées)
### 4.3 Architecture (Impact : Élevé)
8. **Splitter `app.js` en modules ES :**
```
frontend/
js/
core.js (init, state global, api helper)
search.js (QueryParser, AutocompleteDropdown, SearchHistory)
tabs.js (TabManager)
dashboard.js (showWelcome, widgets)
editor.js (CodeMirror wrapper)
auth.js (AuthManager, login screen)
find.js (FindInPageManager)
ui.js (helpers: el(), icon(), toast, etc.)
```
Puis dans `index.html` :
```html
<script type="module" src="/static/js/app.js"></script>
```
9. **Extraire la config sensible vers `.env` :**
```bash
# .env (gitignored)
OBSIGATE_ADMIN_PASSWORD=***
# .env.example (commité, sans secrets)
OBSIGATE_ADMIN_PASSWORD=
```
10. **Ajouter `.dockerignore` :**
```
.git
.gitignore
__pycache__
*.pyc
.venv
test_vault
docs
*.md
!README.md
docker-compose.yml
.env
```
### 4.4 Fonctionnalités différenciantes (Impact : Moyen-Élevé)
11. **Full-text search avec stemming français :**
```python
from snowballstemmer import FrenchStemmer
stemmer = FrenchStemmer()
# "mangé" → "mang", "chevaux" → "cheval"
```
Actuellement TF-IDF tokenise mais ne stemme pas, donc "manger" et "mangé" sont des tokens différents.
12. **Mode hors-ligne PWA complet :** Mettre en cache l'index via IndexedDB pour une recherche offline avec synchronisation lors du retour en ligne.
13. **Plugin système :** Permettre des extensions utilisateur (comme Obsidian plugins) — custom renderers, custom search operators, custom dashboard widgets.
14. **Graph view interactive** (code backend déjà présent !) : Les modèles `GraphNode`, `GraphEdge`, `GraphResponse` existent. Finaliser l'endpoint et l'UI pour une vue graphique des wikilinks.
15. **Collaboration temps réel :** Via WebSocket — édition simultanée avec CRDT (Yjs/Automerge), présence utilisateur, commentaires.
### 4.5 Sécurité & Opérations (Impact : Moyen)
16. **Rotation des logs Python** avec `RotatingFileHandler` — actuellement pas de limite de taille sur les logs.
17. **OAuth2/OIDC** en plus du JWT local (Google, GitHub, authentification SSO).
18. **Health check enrichi :** Ajouter l'état de l'index (size, readiness), mémoire utilisée, uptime, nombre de clients SSE.
### 4.6 UX/UI (Impact : Moyen)
19. **Palette de commandes** (`Ctrl+P` style VS Code) pour navigation rapide.
20. **Drag & drop de fichiers** dans l'arborescence pour déplacer/réorganiser.
21. **Diff viewer** pour comparer les versions backupées (`.obsigate-backup/`).
22. **Fichiers récents par vault** dans le dashboard (filtrer par vault actif).
---
## 5. MATRICE DE PRIORITÉ
| Rang | Action | Effort | Impact | Risque si ignoré |
|------|--------|--------|--------|------------------|
| 1 | Tests unitaires backend (pytest) | 3-5 jours | 🔴 Critique | Régressions silencieuses |
| 2 | CI/CD pipeline | 1-2 jours | 🔴 Critique | Bugs en production |
| 3 | `.dockerignore` | 15 min | 🟠 Élevé | Fuite de code |
| 4 | `.env` pour secrets | 10 min | 🟠 Élevé | Secret dans le repo |
| 5 | Compression HTTP (GZip) | 5 min | 🟠 Élevé | Bande passante gaspillée |
| 6 | Cache-Control headers | 30 min | 🟠 Élevé | Requêtes inutiles |
| 7 | Splitter `app.js` en modules | 2-3 jours | 🟡 Moyen | Dette technique |
| 8 | `sortedcontainers` pour index | 2 heures | 🟡 Moyen | Performance à l'échelle |
| 9 | Stemming français | 2-4 heures | 🟡 Moyen | Qualité recherche |
| 10 | Mode hors-ligne PWA | 3-5 jours | 🟡 Moyen | Feature gap |
| 11 | OAuth2/OIDC | 2-3 jours | 🟢 Faible | Adoption entreprise |
| 12 | Plugin système | 5-10 jours | 🟢 Faible | Extensibilité |
---
## 6. VERDICT GLOBAL
ObsiGate est un projet **impressionnant par sa complétude et sa qualité** pour une codebase de cette taille développée en solo. Les fondamentaux sont solides :
- ✅ Sécurité bien traitée (path traversal, JWT, Argon2id, rate limiting, audit, backup)
- ✅ Architecture propre et cohérente (séparation backend/frontend, modules bien découpés côté backend)
- ✅ Fonctionnalités riches (50+ endpoints, recherche TF-IDF, PWA, éditeur intégré)
- ✅ Attention aux détails (docstrings, type hints, Pydantic models documentés, logging structuré)
- ✅ Choix techniques judicieux (FastAPI, CodeMirror 6, vanilla JS, multi-stage Docker)
Les axes d'amélioration sont clairs et actionnables. Le passage de "bon projet" à "projet professionnel de niveau production" passe par **trois piliers :**
1. **Tests** — c'est le maillon faible critique, zéro test aujourd'hui
2. **CI/CD** — automatiser la qualité pour chaque commit
3. **Modularisation du frontend** — préparer la scalabilité du code
Le reste (OAuth, stemming, mode hors-ligne) sont des fonctionnalités qui différencieront ObsiGate des alternatives comme Obsidian Publish.
### Note technique
| Dimension | Note | Commentaire |
|-----------|------|-------------|
| Architecture | 8/10 | Propre, bien pensée, scalable |
| Sécurité | 8/10 | Excellentes bases, quelques améliorations possibles |
| Qualité du code | 7/10 | Bon backend, frontend monolithique |
| Performance | 7/10 | Index mémoire efficace, compression manquante |
| Tests | 0/10 | **Aucun test** — priorité absolue |
| Documentation | 7/10 | README excellent, docs/ fourni, manque CHANGELOG |
| DevOps | 6/10 | Docker OK, pas de CI/CD, pas de .dockerignore |
| **Global** | **7.5/10** | Excellent pour un projet solo, potentiel 9/10 |
---
*Rapport généré le 27 mai 2026 par audit automatisé Hermes Agent.*
*Ce document complète `ANALYSE_REVIEW.md` (bugs dashboard/search) et `ROADMAP.md` (fonctionnalités planifiées).*