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>
12 KiB
Revue securite Phase 2
Date: 2026-05-06
Base: SECURITY_REVIEW_AFTER_FIXES.md, SECURITY_FIXES_APPLIED_PHASE2.md, code actuel.
Mode: revue statique locale + builds + controles cibles en conteneur. Aucun fichier applicatif n'a ete modifie.
Synthese
Les corrections Phase 2 principales sont bien presentes dans le code actuel: rattrapage admin utilisant encore admin, token_version JWT, expiration 24h, CORS configurable, creation des nouvelles cles JWT en 0600, build frontend reproductible via npm ci, conteneurs Compose avec privileges reduits, et documentation globalement enrichie.
Deux points importants restent a corriger:
- La suite de tests Phase 2 n'est pas reproductible telle que declaree:
backend/requirements-test.txtlaisse installerhttpx>=0.28, incompatible avec leTestClientde Starlette utilise par FastAPI 0.104.1. - SEC-FIX-010 ne corrige que les nouvelles creations de
secret_key.txt; un fichier existant cree avant Phase 2 avec des permissions trop larges est relu tel quel, sanschmod 0600.
Verification executee:
npm run builddansfrontend: OK.docker compose build: OK.- Controles conteneur: backend sous UID/GID
1000:1000, frontend sous UID101, ping OK aveccap_drop: ALL+cap_add: NET_RAW, secret auto-genere en0600, bootstrap admin OK, rattrapage admin existant OK. pytest -q testsavecrequirements-test.txtactuel: ECHEC, incompatibilitehttpx.pytest -q testsavecpytest>=7.4ethttpx<0.28: OK, 21 tests passes.
Verification des corrections Phase 2
SEC-FIX-001 - Bootstrap admin et rattrapage des bases existantes
Statut: corrige
Preuves:
backend/main.py:65-77ajoutemust_change_passwordaux bases existantes.backend/main.py:80-92ajoutetoken_version.backend/main.py:95-111forcemust_change_password=1siadminutilise encore le mot de passe bootstrapadmin.backend/main.py:114-146cree une base neuve avecadmin/admin+must_change_password=1, ou avecINITIAL_ADMIN_PASSWORD+must_change_password=0.backend/main.py:179-183protege les routeurs metier viarequire_password_changed.- Controle conteneur: base neuve ->
admin 1 1; ancienne base admin/admin avecmust_change_password=0->must_change_password=1; admin avec mot de passe personnalise -> non modifie.
Point de vigilance:
SECURITY_FIXES_APPLIED_PHASE2.mddit que_migrate_force_admin_password_change()s'execute avant_migrate_users(). C'est vrai dans le code (backend/main.py:149-155) et correct pour les bases existantes. Pour une base neuve, le rattrapage ne s'applique pas car la table n'existe pas encore; le cas est couvert par_migrate_users().
SEC-FIX-004 - Invalidation de session apres changement de mot de passe
Statut: corrige
Preuves:
backend/models.py:13ajouteUser.token_version.backend/routers/auth.py:44reduit l'expiration JWT a 24 heures.backend/routers/auth.py:84-90inclutverdans le JWT.backend/routers/auth.py:93-107rejette un token dontverdiffere deuser.token_version.backend/routers/auth.py:175-180incrementetoken_versionlors d'un changement de mot de passe.- Les tests d'invalidation passent quand la dependance
httpxest pinnee correctement.
Limite restante:
- Le JWT reste stocke dans
localStorage(frontend/src/auth.js:3-17) et lu par JavaScript (frontend/src/api.js:6-9). Phase 2 reduit la duree et invalide les anciennes sessions, mais ne supprime pas le risque de vol de token en cas de XSS.
SEC-FIX-005 - CORS configurable
Statut: corrige avec risque residuel de configuration
Preuves:
backend/main.py:163-177litALLOWED_ORIGINS, supporte*, chaine vide, ou CSV d'origines..env.example:25-35documente la variable.
Risque residuel:
- Le defaut reste permissif:
ALLOWED_ORIGINS=*dans.env.example:35etdocker-compose.yml:18. C'est retrocompatible, mais une installation exposee directement garde CORS ouvert si l'operateur ne change rien.
SEC-FIX-010 - Secret JWT
Statut: partiellement corrige
Preuves:
backend/routers/auth.py:31-38cree les nouveaux secrets viaos.open(..., 0o600).- Controle conteneur: secret auto-genere avec mode
0o600. .gitignore:1-8ignoredb_data/,*.db,.env.
Risque restant:
backend/routers/auth.py:27-28lit un fichier existant sans verifier ni corriger ses permissions. Une installation ayant deja creedb_data/secret_key.txtavant Phase 2 avec un mode trop large conserve ce mode apres mise a jour.
Recommandation:
- Sur chargement du fichier, appliquer ou au minimum verifier
chmod 0600. - Documenter une remediation explicite:
chmod 600 db_data/secret_key.txt.
SEC-FIX-011 - Durcissement conteneurs
Statut: corrige pour le chemin Docker Compose fourni
Preuves:
docker-compose.yml:4-8lance le backend sousDOCKER_UID:DOCKER_GID, aveccap_drop: ALLetcap_add: NET_RAWuniquement.docker-compose.yml:35-41ajoute un healthcheck backend.docker-compose.yml:45-62lance le frontend sur8080, aveccap_drop: ALL,no-new-privilegeset healthcheck.frontend/Dockerfile:4utilisenpm ci.frontend/Dockerfile:8-12utilisenginxinc/nginx-unprivileged:alpine.frontend/nginx.conf:2ecoute sur8080.- Controle conteneur: backend
uid=1000 gid=1000; frontenduid=101(nginx); ping127.0.0.1OK avec la configuration de capabilities.
Limites:
backend/Dockerfilene definit pasUSER; le non-root depend de Compose. Un lancement direct de l'image backend sans--usertournera en root.- Le backend n'a pas
no-new-privileges, par choix documente pour conserver le fonctionnement deping.
Regressions et nouveaux risques
R1 - Tests Phase 2 non reproductibles avec requirements-test.txt
Severite: moyenne
backend/requirements-test.txt:1-2 contient:
pytest>=7.4
httpx>=0.25
Dans le conteneur backend actuel, cela installe un httpx recent. Resultat: tous les tests utilisant TestClient(app) echouent avec:
TypeError: Client.__init__() got an unexpected keyword argument 'app'
Cause: incompatibilite entre Starlette/FastAPI du projet et httpx>=0.28, qui a retire le raccourci app=.
Verification:
- Avec
requirements-test.txtactuel: 1 failed, 20 errors. - Avec
httpx<0.28: 21 passed.
Correction recommandee:
- Pinner
httpx<0.28dansbackend/requirements-test.txt, ou mettre a jour FastAPI/Starlette de maniere controlee.
R2 - Option Docker secret probablement fragile avec backend non-root
Severite: moyenne
docker-compose.yml:31-34 documente une option de secret montee vers /app/data/secret_key.txt avec mode: 0400, pendant que le backend tourne sous DOCKER_UID:DOCKER_GID (docker-compose.yml:4). Selon le comportement exact de Docker Compose et du proprietaire du fichier source, un secret root-owned en 0400 peut devenir illisible par le backend non-root au demarrage.
Correction recommandee:
- Tester explicitement cette variante.
- Si elle est conservee, specifier
uid/gidcompatibles quand Compose le permet, ou supporter une variableSECRET_KEY_FILEpointant vers un chemin de secret lisible par l'utilisateur applicatif.
R3 - Nouvelles cles protegees, anciennes cles non remediées
Severite: moyenne
Voir SEC-FIX-010. Le risque est particulierement important pour les installations deja deployees avant Phase 2, c'est-a-dire precisement le cas traite par les migrations de rattrapage admin.
Incoherences documentation / code
docs/extending.md:70-71recommande encoredependencies=[Depends(get_current_user)]pour un nouveau routeur. Le code actuel doit utiliserrequire_password_changedpour les routeurs metier.docs/backend.md:5-12dit "four router groups" mais l'extrait montrevlans,devices,discoveryet ometlinks, pourtant enregistre dansbackend/main.py:182.docs/frontend.md:48-55documentelinksApi, maisfrontend/src/api.jsn'exporte paslinksApi.docs/frontend.md:31documentesetAuth(token, username)alors que le code actuel attendsetAuth(token, username, mustChange)(frontend/src/auth.js:11).docs/frontend.md:114-121decrit un guardv-elsedirect et des callbacks sansmustChangePassword; le code actuel a la branche forceeAccountModal(frontend/src/App.vue:3-11) et propagemustChangePassword(frontend/src/App.vue:145-153).docs/architecture.md:54-57dit encore que le stage final frontend estnginx:alpine; le code utilisenginxinc/nginx-unprivileged:alpine(frontend/Dockerfile:8).docs/architecture.md:62-67liste une sequence de demarrage backend obsolete et omet les migrations_migrate_users_must_change_password,_migrate_users_token_version,_migrate_force_admin_password_change.AGENTS.md:207parle d'un "Docker volumedb_data"; le Compose actuel utilise un bind mount./db_data:/app/data(docker-compose.yml:10-11)..env.example:5contient un caractere corrompu dans le commentaire de section. Impact faible, mais a nettoyer.SECURITY_FIXES_APPLIED_PHASE2.mdgarde des traces de l'approche abandonnee (APP_UID/APP_GID,DAC_OVERRIDE, mentions backend Dockerfile) alors que le code final utiliseDOCKER_UID/DOCKER_GIDvia Compose et a supprimeDAC_OVERRIDE.
Points herites confirmes ouverts
Ces points etaient deja signales dans SECURITY_REVIEW_AFTER_FIXES.md et restent ouverts dans le code actuel:
| ID | Etat actuel |
|---|---|
| SEC-FIX-006 | Discovery: PingRequest.ips non borne (backend/routers/discovery.py:72-85), DNS libre (backend/routers/discovery.py:52-60), limite par reseau mais pas limite totale multi-target (backend/routers/discovery.py:93-107). |
| SEC-FIX-007 | Validation metier faible: champs libres dans devices.py, vlans.py, links.py; URL/IP/CIDR/couleur/type non contraints cote serveur. Les rel="noreferrer noopener" sont en revanche corriges dans les liens frontend. |
| SEC-FIX-008 | En-tetes HTTP de securite absents dans frontend/nginx.conf: pas de CSP, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, ni politique frame. |
| SEC-FIX-009 | Le Compose expose encore 8080:8080 sur toutes les interfaces par defaut (docker-compose.yml:45-46). README documente HTTPS et bind loopback, mais ce n'est pas le defaut. |
| SEC-FIX-012 | Pas de logs d'audit structures dans les routeurs backend. |
| SEC-FIX-013 | SQLite foreign keys non forcees par connexion: pas d'event SQLAlchemy PRAGMA foreign_keys=ON dans backend/database.py. |
| SEC-FIX-014 | cytoscape a ete supprime de frontend/package.json, mais reste dans frontend/package-lock.json:12 et dans le node_modules local comme dependance extraneous. Le lockfile doit etre regenere, et aucun audit dependances automatise n'est documente/integre. |
| SEC-FIX-015 | Import JSON frontend sans limite de taille ni schema, erreurs ignorees par .catch(() => {}) (frontend/src/App.vue:190-207). |
| SEC-FIX-016 | v-html reste present pour les icones d'onglet (frontend/src/App.vue:30). Les valeurs sont hardcodees localement (frontend/src/App.vue:139-143), donc risque XSS faible aujourd'hui, mais surface inutile. |
Conclusion
Phase 2 corrige effectivement les risques prioritaires annonces autour du bootstrap admin, de l'invalidation JWT, du CORS configurable, du secret JWT pour nouvelles installations et du durcissement Compose.
Avant de considerer Phase 2 comme stabilisee, corriger en priorite:
backend/requirements-test.txt: pinnerhttpx<0.28ou mettre a jour FastAPI/Starlette.- Remediation des permissions des secrets existants (
chmod 0600au chargement ou migration). - Clarification/test de l'option Docker secret avec backend non-root.
- Mise a jour des incoherences docs/code listees ci-dessus.