- 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)
385 lines
17 KiB
Markdown
385 lines
17 KiB
Markdown
# 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).*
|