Files
stupid-simple-network-inven…/SECURITY_REVIEW_AFTER_FIXES.md
T
olivier 88cf6458d0 Initial commit — Stupid Simple Network Inventory
Application web d'inventaire réseau manuel avec FastAPI, Vue 3 et Docker.
Inclut l'authentification JWT, la découverte ICMP, et la topologie en cards CSS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 09:19:19 +02:00

311 lines
15 KiB
Markdown

# Revue securite apres corrections
Date: 2026-05-06
Base de comparaison: `SECURITY_AUDIT_BASE.md`, `SECURITY_FIX_PLAN_FOR_CLAUDE.md`, `SECURITY_FIXES_APPLIED.md` et code actuel.
Mode: revue statique locale, sans exploitation. Aucun fichier applicatif n'a ete modifie.
## Synthese
Les corrections declarees dans `SECURITY_FIXES_APPLIED.md` sont presentes dans le code pour les trois premiers sujets: changement force au premier login, rate limiting du login, validation minimale du changement de compte. Elles reduisent le risque principal de bootstrap et de bruteforce.
La correction SEC-FIX-001 reste partielle pour les installations deja creees avant la migration: l'ajout de colonne met `must_change_password` a `0` par defaut. Dans la base locale observee, l'utilisateur `admin` a `must_change_password=0`. Si ce compte utilise encore l'ancien mot de passe public, il ne sera pas force a changer. La documentation est egalement en retard sur le code: plusieurs fichiers decrivent encore `get_current_user` comme dependance des routeurs metier et ne documentent pas le nouveau flux `must_change_password`.
Verification effectuee:
- Parsing Python par AST: OK, 9 fichiers parses.
- Build frontend Vite vers `/tmp/topologie-vite-build`: OK.
- Aucun test automatise dedie n'a ete trouve dans `backend` ou `frontend`.
## SEC-FIX-001 - Changement de mot de passe obligatoire au premier login
Statut: partiellement corrige
Preuve dans le code:
- `backend/models.py:6-12` ajoute `User.must_change_password`.
- `backend/main.py:78-110` cree `admin` avec `must_change_password=1` si `INITIAL_ADMIN_PASSWORD` est absent, et `0` si la variable est fournie.
- `backend/main.py:130-133` protege les routeurs metier avec `Depends(require_password_changed)`.
- `backend/routers/auth.py:97-100` refuse les routeurs proteges avec 403 si le changement est requis.
- `backend/routers/auth.py:149-172` remet `must_change_password` a `False` apres changement de mot de passe.
- `frontend/src/App.vue:3-11` affiche `AccountModal` forcee avant l'application si `mustChangePassword` est vrai.
- `frontend/src/components/AccountModal.vue:93-97` exige un nouveau mot de passe cote client en mode force.
Risque restant:
- `backend/main.py:63-75` ajoute la colonne aux bases existantes avec `DEFAULT 0`; les comptes deja presents ne sont donc pas forces a changer.
- La base locale contient `admin|0` pour `must_change_password`.
- `docker-compose.yml` ne montre pas `INITIAL_ADMIN_PASSWORD`; `.env.example` est absent.
- `AGENTS.md`, `CLAUDE.md` et `docs/backend.md` decrivent encore l'ancien modele `Depends(get_current_user)` ou l'ancien contrat de login.
Recommandation finale:
- Ajouter une migration de rattrapage explicite pour les anciennes bases, ou documenter une commande de remediation obligatoire.
- Documenter `INITIAL_ADMIN_PASSWORD`, `must_change_password` et `require_password_changed` dans `AGENTS.md`, `CLAUDE.md`, `docs/*` et `.env.example`.
- Ajouter un test couvrant base vide, base existante et acces CRUD avant/apres changement.
## SEC-FIX-002 - Rate limiting sur POST /api/auth/login
Statut: corrige
Preuve dans le code:
- `frontend/rate_limit.conf:1` definit `limit_req_zone`.
- `frontend/Dockerfile:10-11` copie `nginx.conf` et `rate_limit.conf`.
- `frontend/nginx.conf:6-13` applique `limit_req zone=login burst=5 nodelay` et retourne 429 sur `/api/auth/login`.
- `backend/routers/auth.py:43-76` implemente un rate limit in-memory par username et par IP.
- `backend/routers/auth.py:132-146` applique ces controles avant verification du mot de passe.
- `frontend/src/components/LoginPage.vue:69-74` affiche un message dedie sur 429.
Risque restant:
- Le rate limit backend est en memoire: il est perdu au redemarrage et se fragmente avec plusieurs workers/process.
- `Request.client.host` voit souvent l'adresse du proxy Nginx, pas le client final; le controle IP backend peut donc devenir global derriere proxy. Le controle Nginx compense dans le deploiement Compose fourni.
- Les compteurs IP backend ne sont pas remis a zero apres login reussi.
- Aucun test automatise de fenetre, expiration ou non-enumeration n'a ete trouve.
Recommandation finale:
- Conserver le controle Nginx et soit documenter que le backend ne doit pas etre expose directement, soit gerer proprement `X-Forwarded-For` depuis un proxy de confiance.
- Ajouter des tests API pour echecs successifs, expiration de fenetre et succes apres expiration.
## SEC-FIX-003 - Validation serveur des entrees sur PUT /api/auth/account
Statut: partiellement corrige
Preuve dans le code:
- `backend/routers/auth.py:103-117` valide username et mot de passe.
- `backend/routers/auth.py:157-165` applique ces validations avant modification.
- `frontend/src/components/AccountModal.vue:91-117` applique une validation miroir.
- `frontend/src/i18n.js:160-165`, `310-315`, `460-465` contient les messages localises.
Risque restant:
- La politique mot de passe reste minimale: `password1` ou `admin123` respectent la regle lettre+chiffre et longueur 8.
- `AccountUpdate` accepte les champs optionnels sans contraintes Pydantic; la robustesse depend des validations manuelles.
- Les erreurs `Username already taken` et `Current password is incorrect` ne sont pas sous forme de codes stables et restent moins homogenes que les nouveaux codes.
- Aucun test API dedie n'a ete trouve.
Recommandation finale:
- Renforcer la politique avec une liste de mots de passe evidents/refuses et des contraintes Pydantic (`Field`, `StringConstraints`) en plus des helpers.
- Normaliser/trim le username avant comparaison et stockage.
- Ajouter des tests pour mot de passe faible evident, username invalide, doublon et cas valide.
## SEC-FIX-004 - JWT localStorage, expiration et invalidation
Statut: non corrige
Preuve dans le code:
- `frontend/src/auth.js:3-17` stocke toujours le JWT dans `localStorage`.
- `frontend/src/api.js:6-9` continue d'envoyer un bearer token lu par JavaScript.
- `backend/routers/auth.py:38-80` conserve une expiration de 7 jours et un payload `{sub, exp}` sans version de session.
- `backend/routers/auth.py:162-168` change le mot de passe sans invalider les tokens deja emis.
Risque restant:
- Vol/rejeu de JWT en cas de XSS, extension compromise ou poste compromis.
- Ancien token toujours valide apres changement de mot de passe jusqu'a expiration.
Recommandation finale:
- Migrer vers cookie `HttpOnly`, `Secure`, `SameSite` ou, a minima, ajouter `session_version`/`token_version` en base et reduire l'expiration.
## SEC-FIX-005 - CORS configurable ou supprime
Statut: non corrige
Preuve dans le code:
- `backend/main.py:122-127` conserve `allow_origins=["*"]`, `allow_methods=["*"]`, `allow_headers=["*"]`.
- `.env.example` est absent.
Risque restant:
- Configuration permissive si le backend est publie directement ou si l'auth migre vers cookies.
Recommandation finale:
- Supprimer CORS en mode meme origine ou ajouter `ALLOWED_ORIGINS` explicite.
- Documenter que le backend doit rester interne.
## SEC-FIX-006 - Encadrement discovery scan/ping
Statut: non corrige
Preuve dans le code:
- `backend/routers/discovery.py:72-86` accepte `ips: list[str]` sans validation IP ni limite de taille.
- `backend/routers/discovery.py:89-129` valide seulement le format CIDR et limite chaque reseau a 1024 hotes, mais pas le total de targets ni le perimetre autorise.
- `backend/routers/discovery.py:52-60` accepte un serveur DNS fourni librement.
Risque restant:
- Reconnaissance reseau authentifiee, surcharge CPU/process et DNS arbitraire.
Recommandation finale:
- Valider IP/CIDR/DNS, plafonner le total par requete, autoriser seulement les CIDR d'inventaire ou une allowlist, et ajouter rate limit.
## SEC-FIX-007 - Validation stricte des modeles metier
Statut: non corrige
Preuve dans le code:
- `backend/routers/devices.py:11-33`, `backend/routers/vlans.py:11-15`, `backend/routers/links.py:11-15` utilisent encore des `str` libres sans bornes, enums ni validation URL/IP/CIDR/couleur.
- Les liens externes ont `rel="noopener"` mais pas `noreferrer`: `frontend/src/components/DeviceManager.vue:137`, `frontend/src/components/TopologyGraph.vue:35`, `56`, `104`, `142`.
Risque restant:
- Donnees incoherentes, champs enormes, URL dangereuses et surface XSS future.
Recommandation finale:
- Ajouter contraintes Pydantic v2, enums serveur, validation URL `http/https`, IP/CIDR et couleur hex.
- Remplacer `rel="noopener"` par `rel="noreferrer noopener"`.
## SEC-FIX-008 - En-tetes de securite HTTP
Statut: non corrige
Preuve dans le code:
- `frontend/nginx.conf:1-25` ne definit pas CSP, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, `X-Frame-Options` ou `frame-ancestors`.
- La seule configuration ajoutee concerne `limit_req`: `frontend/nginx.conf:6-8`.
Risque restant:
- Durcissement navigateur absent dans la configuration fournie.
Recommandation finale:
- Ajouter les en-tetes dans Nginx ou fournir une configuration reverse-proxy de reference verifiee.
## SEC-FIX-009 - Contrat TLS/reverse-proxy
Statut: non corrige
Preuve dans le code:
- `docker-compose.yml:14-15` expose toujours `"8080:80"` sur toutes les interfaces.
- Aucune `.env.example` ni documentation de bind local/reverse proxy TLS n'a ete ajoutee.
Risque restant:
- Exposition HTTP involontaire sur un LAN non fiable avec interception d'identifiants/tokens.
Recommandation finale:
- Documenter HTTPS obligatoire hors local et recommander `127.0.0.1:8080:80` pour un proxy local.
- Fournir un exemple reverse-proxy TLS.
## SEC-FIX-010 - Gestion du secret JWT
Statut: non corrige
Preuve dans le code:
- `backend/routers/auth.py:20-33` cree toujours `data/secret_key.txt` via `open(..., "w")` sans permissions explicites.
- `docker-compose.yml:6-7` monte toujours `./db_data:/app/data`.
- `.gitignore` et `.env.example` sont absents.
Risque restant:
- Fuite locale de cle JWT et forge de tokens jusqu'a rotation.
Recommandation finale:
- Creer le fichier avec permissions `0600`, ignorer `db_data/`, documenter rotation et supporter un secret Docker/env obligatoire en production.
## SEC-FIX-011 - Durcissement conteneurs
Statut: non corrige
Preuve dans le code:
- `backend/Dockerfile:1-8` et `frontend/Dockerfile:8-12` ne definissent pas d'utilisateur non-root.
- `docker-compose.yml:1-20` ne definit pas `cap_drop`, `security_opt`, `read_only`, limites ressources ni healthchecks.
Risque restant:
- Impact augmente en cas de compromission d'un conteneur.
Recommandation finale:
- Passer en non-root, ajouter `cap_drop: [ALL]`, garder `NET_RAW` seulement pour le backend, ajouter `no-new-privileges`, healthchecks et volumes temporaires explicites.
## SEC-FIX-012 - Logs securite structures
Statut: non corrige
Preuve dans le code:
- Aucun logging structure n'est present dans `backend/routers/auth.py`, `devices.py`, `vlans.py`, `links.py` ou `discovery.py`.
Risque restant:
- Investigation limitee en cas d'incident: login, scans, suppressions et rate limits ne sont pas tracables.
Recommandation finale:
- Ajouter logs structures sans secrets pour authentification, changements de compte, CRUD, scans et rate limits.
## SEC-FIX-013 - Foreign keys SQLite
Statut: non corrige
Preuve dans le code:
- `backend/database.py:7-10` cree l'engine sans event SQLAlchemy pour `PRAGMA foreign_keys=ON`.
- Les `ForeignKey` restent declares dans `backend/models.py`, mais SQLite ne les enforce pas sans pragma par connexion.
Risque restant:
- Incoherences relationnelles possibles via evolutions futures, migrations ou erreurs applicatives.
Recommandation finale:
- Activer `PRAGMA foreign_keys=ON` via event SQLAlchemy et tester suppressions/creation d'interfaces/liens invalides.
## SEC-FIX-014 - Supply chain et builds reproductibles
Statut: non corrige
Preuve dans le code:
- `frontend/Dockerfile:3-4` utilise toujours `npm install` au lieu de `npm ci`.
- `frontend/package.json` contient encore `cytoscape`.
- Aucune procedure d'audit dependances n'a ete ajoutee.
Risque restant:
- Builds moins reproductibles et dependances inutiles/vulnerables plus difficiles a suivre.
Recommandation finale:
- Utiliser `npm ci`, supprimer `cytoscape` s'il est inutilise, documenter `npm audit` et l'audit Python en conservant le pin `bcrypt==3.2.2`.
## SEC-FIX-015 - Import JSON borne et valide
Statut: non corrige
Preuve dans le code:
- `frontend/src/App.vue:198-220` lit le fichier complet, parse sans schema, importe en boucle et ignore silencieusement les erreurs par `.catch(() => {})`.
Risque restant:
- Import incoherent, erreurs masquees et deni de service local par fichier volumineux.
Recommandation finale:
- Ajouter limite de taille, validation de schema, compteur d'erreurs et retour utilisateur detaille.
## SEC-FIX-016 - Suppression de v-html
Statut: non corrige
Preuve dans le code:
- `frontend/src/App.vue:30` utilise encore `v-html="tab.icon"`.
Risque restant:
- Risque XSS defense-in-depth si une future icone devient dynamique ou non controlee.
Recommandation finale:
- Remplacer par texte, composants d'icones ou SVG statique sans `v-html`.
## SEC-FIX-017 - Endpoint health public documente ou restreint
Statut: partiellement corrige
Preuve dans le code:
- `backend/main.py:136-138` expose toujours `/api/health` publiquement avec seulement `{"status": "ok"}`, sans detail sensible.
- La decision de securite n'est pas documentee dans les fichiers lus.
Risque restant:
- Exposition inutile d'information de disponibilite si le proxy publie trop largement l'API.
Recommandation finale:
- Documenter explicitement que l'endpoint est public et minimal, ou le restreindre au proxy/healthcheck.
## SEC-FIX-018 - Documentation securite et environnement
Statut: non corrige
Preuve dans le code:
- `README.md` et `.env.example` sont absents.
- `AGENTS.md:66`, `CLAUDE.md:171` et `docs/backend.md:5-12` decrivent encore les routeurs metier avec `get_current_user` au lieu de `require_password_changed`.
- `docs/backend.md:28-36` ne reflete pas le nouveau champ `must_change_password` ni les erreurs de validation.
Risque restant:
- Operateur guide par une documentation obsolete, avec configuration dev/prod et secrets insuffisamment explicites.
Recommandation finale:
- Ajouter `README.md` et `.env.example`, puis mettre a jour `AGENTS.md`, `CLAUDE.md` et `docs/*` pour `INITIAL_ADMIN_PASSWORD`, `SECRET_KEY`, CORS, TLS, rotation de cle, sauvegarde/restauration et profils dev/prod.
## Conclusion
Les trois corrections declarees sont globalement integrees, mais seules SEC-FIX-002 peut etre consideree corrigee avec des risques residuels acceptables pour le mode Compose actuel. SEC-FIX-001 et SEC-FIX-003 restent partielles. Les autres taches du plan ne sont pas encore appliquees.
Priorite recommandee:
1. Corriger la migration/strategie de rattrapage pour les comptes existants `admin`.
2. Mettre a jour la documentation et ajouter `.env.example`.
3. Ajouter des tests API pour auth, rate limit et changement force.
4. Continuer les corrections P1 restantes: JWT/session, discovery, validation metier et secret JWT.