Files
stupid-simple-network-inven…/SECURITY_AUDIT_BASE.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

19 KiB

Audit de securite de base

Projet audite: Stupid Simple Network Inventory
Date: 2026-05-05
Referentiels: OWASP ASVS niveau 1, OWASP Top 10 2021
Mode: revue statique locale, sans attaque reelle, sans exploitation, sans correction applicative.

Synthese

L'application est une SPA Vue exposee par Nginx, avec API FastAPI protegee par JWT pour les routeurs metier. Le modele d'exploitation precise est local, derriere un reverse-proxy. Dans ce modele, certains controles comme TLS, filtrage d'exposition et en-tetes HTTP peuvent legitimement etre portes par le reverse-proxy, mais doivent etre documentes et verifiables.

Risque global estime: moyen en usage local derriere reverse-proxy correctement configure; eleve si le service est expose directement a un LAN non fiable ou a Internet. Le risque principal vient du bootstrap avec identifiants publics admin/admin si le changement n'est pas force avant exposition, de l'absence de limitation de tentatives, du stockage du JWT en localStorage, de la surface de scan reseau authentifiee et d'un manque de documentation du contrat de securite attendu cote reverse-proxy.

Perimetre lu

  • Documents: CLAUDE.md, AGENTS.md, docs/*.md
  • README: README.md absent
  • Config: docker-compose.yml, backend/Dockerfile, frontend/Dockerfile, frontend/nginx.conf, frontend/vite.config.js, backend/requirements.txt, frontend/package.json, frontend/package-lock.json
  • Backend: backend/main.py, backend/database.py, backend/models.py, backend/routers/*.py
  • Frontend: frontend/src/**/*.js, frontend/src/**/*.vue, frontend/index.html
  • .env.example: aucun fichier trouve

Points positifs

  • Les routeurs metier vlans, devices, links, discovery sont proteges par Depends(get_current_user) dans backend/main.py:105-108.
  • Les requetes SQL passent par SQLAlchemy ou par text() parametre pour les migrations; pas de concatenation SQL dangereuse observee dans les routes CRUD.
  • Le ping utilise subprocess.run([...]) sans shell=True dans backend/routers/discovery.py:42-46, ce qui limite fortement l'injection de commande shell.
  • Les mots de passe sont haches avec bcrypt via passlib dans backend/routers/auth.py:37.
  • La cle JWT est generee aleatoirement au premier demarrage si SECRET_KEY est absent dans backend/routers/auth.py:20-30.
  • Les liens externes visibles utilisent rel="noopener" dans les composants principaux, ce qui reduit le risque de controle de la fenetre ouvrante.

Constats principaux

SEC-01 - Bootstrap administrateur admin/admin sans changement force

Priorite: P0
OWASP: Top 10 A07 Identification and Authentication Failures, A04 Insecure Design
ASVS L1: authentification, gestion des secrets initiaux

Fichiers:

  • backend/main.py:63-86
  • CLAUDE.md / AGENTS.md section Authentication
  • docs/backend.md, docs/extending.md

La migration _migrate_users() cree automatiquement un compte admin avec mot de passe admin quand la table users est vide. Les documents indiquent explicitement ces identifiants comme mot de passe de depart a changer. Ce comportement est donc intentionnel, mais reste un ecart ASVS si l'application peut etre atteinte avant changement manuel: toute instance nouvellement demarree est accessible avec des identifiants publics jusqu'a cette action.

Impact: compromission complete des donnees d'inventaire, utilisation de la fonctionnalite de scan reseau, modification/suppression d'equipements.

Recommandation: conserver ce bootstrap seulement si l'application force le changement au premier login ou si la documentation exige explicitement une premiere connexion depuis localhost/reseau de confiance avant toute exposition. Variante plus robuste: exiger un mot de passe initial via variable d'environnement, fichier secret Docker, ou assistant de premier demarrage.

SEC-02 - Pas de limitation de tentatives de connexion

Priorite: P1
OWASP: A07
ASVS L1: protections anti-automatisation de l'authentification

Fichiers:

  • backend/routers/auth.py:72-77
  • frontend/nginx.conf:1-16

POST /api/auth/login ne limite pas les tentatives par IP, par nom utilisateur ou par fenetre temporelle. Nginx ne definit pas non plus de limit_req. Une attaque par devinette de mot de passe devient praticable, surtout pendant la phase de bootstrap avant changement du mot de passe initial.

Recommandation: ajouter une limitation cote API ou Nginx, journaliser les echecs, retourner des erreurs generiques et prevoir un delai progressif ou verrouillage temporaire.

SEC-03 - Politique de mot de passe insuffisante

Priorite: P1
OWASP: A07
ASVS L1: qualite des secrets d'authentification

Fichiers:

  • backend/routers/auth.py:66-69
  • backend/routers/auth.py:80-95
  • frontend/src/components/AccountModal.vue

new_password est accepte s'il est non vide. Aucun minimum de longueur, aucune verification de mot de passe courant compromis/faible, et aucune validation de new_username ne sont appliquees cote serveur.

Recommandation: definir une politique minimale cote serveur, par exemple longueur minimale robuste, refus des mots de passe evidents, normalisation/validation du nom utilisateur, messages d'erreur non enumerants.

SEC-04 - JWT persiste dans localStorage

Priorite: P1
OWASP: A02 Cryptographic Failures, A07, A05 Security Misconfiguration
ASVS L1: protection des tokens de session

Fichiers:

  • frontend/src/auth.js:3-13
  • frontend/src/api.js:6-9
  • backend/routers/auth.py:41-43

Le token JWT est stocke dans localStorage. En cas de XSS ou d'extension navigateur compromise, le token est directement lisible. Le token dure 7 jours et ne contient pas de version de session permettant d'invalider les anciens tokens apres changement de mot de passe.

Recommandation: preferer un cookie HttpOnly, Secure, SameSite, et ajouter une strategie d'expiration courte + renouvellement ou une version de session cote base. Si le mode bearer est conserve, reduire la duree de vie et ajouter une invalidation apres changement de mot de passe.

SEC-05 - CORS ouvert a toutes origines dans l'API interne

Priorite: P2
OWASP: A05
ASVS L1: configuration HTTP securisee

Fichiers:

  • backend/main.py:97-102
  • docs/backend.md indique que ce choix est intentionnel pour un outil LAN

allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] ouvrent l'API a toute origine. Dans le deploiement Compose actuel, le backend n'est pas publie directement et le navigateur passe par le Nginx frontend/reverse-proxy en meme origine, ce qui reduit fortement le risque pratique. Cela reste une configuration permissive a encadrer si un reverse-proxy externe expose /api/ ou si l'auth evolue vers des cookies.

Recommandation: soit supprimer CORS si l'API reste strictement interne et servie en meme origine par le reverse-proxy, soit configurer une liste d'origines explicites via environnement. Documenter le contrat attendu: backend non expose directement, /api/ publie uniquement via le reverse-proxy.

SEC-06 - Surface de scan reseau et SSRF authentifiee

Priorite: P1
OWASP: A10 SSRF, A04 Insecure Design
ASVS L1: validation des cibles reseau et limitation d'abus

Fichiers:

  • backend/routers/discovery.py:22-24
  • backend/routers/discovery.py:52-60
  • backend/routers/discovery.py:79-86
  • backend/routers/discovery.py:89-129
  • docker-compose.yml:4-5

Les endpoints /api/discovery/scan et /api/discovery/ping permettent a tout utilisateur authentifie de provoquer des connexions ICMP et DNS depuis le conteneur backend vers des cibles fournies par le client. scan limite chaque reseau a 1024 hotes, mais ne limite pas le nombre total de targets. ping n'a pas de limite de taille de liste ni de validation IP. Le serveur DNS est librement fourni.

Impact: cartographie de reseaux accessibles depuis le conteneur, charge CPU/process, requetes DNS vers des serveurs arbitraires, usage comme outil de reconnaissance interne apres compromission d'un compte.

Recommandation: valider toutes les IP/CIDR, limiter le nombre total d'adresses par requete, limiter la frequence, autoriser uniquement des plages configurees dans l'inventaire, restreindre les DNS autorises ou utiliser le resolv.conf du conteneur.

SEC-07 - Validation d'entree insuffisante sur les modeles metier

Priorite: P1
OWASP: A03 Injection, A04, A05
ASVS L1: validation cote serveur

Fichiers:

  • backend/routers/devices.py:11-33
  • backend/routers/vlans.py:11-15
  • backend/routers/links.py:11-15
  • frontend/src/components/DeviceManager.vue:137
  • frontend/src/components/TopologyGraph.vue:35,56,104,142

Les schemas Pydantic acceptent des chaines libres sans bornes de longueur ni enums serveur. Exemples: Device.type, virt_type, url, Vlan.cidr, Vlan.color, Link.link_type, noms/descriptions/interfaces. Vue echappe correctement les interpolations texte, mais les URL sont reinjectees en href et l'API peut recevoir des valeurs qui ne passent pas par le formulaire HTML type="url".

Recommandation: ajouter des contraintes Pydantic strictes: longueurs maximales, enums pour types, validation IP/CIDR, validation couleur hex, URL seulement http/https, normalisation des chaines. Ajouter rel="noreferrer noopener" sur les liens externes.

SEC-08 - En-tetes de securite HTTP absents de la configuration fournie

Priorite: P2
OWASP: A05
ASVS L1: durcissement navigateur

Fichiers:

  • frontend/nginx.conf:1-16
  • frontend/index.html

Le Nginx embarque ne configure pas d'en-tetes de securite: CSP, X-Frame-Options ou frame-ancestors, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. Si un reverse-proxy frontal ajoute deja ces en-tetes, le risque est traite au niveau infrastructure; sinon, la configuration fournie reste incomplete.

Recommandation: fournir une configuration de reference pour le reverse-proxy frontal, ou ajouter ces en-tetes dans frontend/nginx.conf. La decision doit eviter les doublons contradictoires et etre documentee.

SEC-09 - TLS delegue au reverse-proxy, contrat non documente

Priorite: P2
OWASP: A02, A05
ASVS L1: protection des identifiants en transit

Fichiers:

  • docker-compose.yml:14-15
  • frontend/nginx.conf:1-2
  • docs/architecture.md

La configuration applicative expose HTTP sur localhost:8080. C'est coherent avec un service local derriere reverse-proxy, a condition que le reverse-proxy frontal termine TLS pour les acces non strictement locaux et que le backend/frontend Compose ne soient pas exposes directement sur un reseau non fiable.

Recommandation: documenter clairement que TLS est une responsabilite du reverse-proxy frontal, fournir un exemple de configuration attendue et recommander un bind local (127.0.0.1:8080:80) lorsque l'application est consommee uniquement par le proxy local.

SEC-10 - Secret JWT persiste dans un bind mount projet, permissions faibles

Priorite: P1
OWASP: A02, A05
ASVS L1: gestion des secrets

Fichiers:

  • backend/routers/auth.py:17-30
  • docker-compose.yml:6-7
  • docs/architecture.md
  • db_data/secret_key.txt present localement, permissions observees 0644

La cle JWT est stockee dans ./db_data/secret_key.txt, dans l'arborescence projet, avec permissions lisibles par d'autres utilisateurs locaux. Elle n'a pas ete lue pendant l'audit. Si cette cle fuit, des JWT valides peuvent etre forges jusqu'a rotation.

Recommandation: utiliser SECRET_KEY depuis un secret Docker ou un fichier hors depot, fixer des permissions strictes, ajouter db_data/ et fichiers secrets a l'exclusion VCS si necessaire, documenter la rotation.

SEC-11 - Conteneurs peu durcis

Priorite: P2
OWASP: A05
ASVS L1: configuration de plateforme

Fichiers:

  • backend/Dockerfile:1-8
  • frontend/Dockerfile:8-11
  • docker-compose.yml:1-26

Les images ne definissent pas d'utilisateur non-root explicite. Compose n'ajoute pas read_only, security_opt: no-new-privileges:true, cap_drop, limites de ressources, healthchecks ou contraintes de filesystem. Le backend ajoute NET_RAW, necessaire au ping, mais sans reduction des autres capacites.

Recommandation: executer les services avec utilisateur non-root, ajouter cap_drop: [ALL] puis cap_add: [NET_RAW] seulement pour backend, activer no-new-privileges, limiter ressources et rendre les FS readonly avec volumes temporaires explicites.

SEC-12 - Pas de journalisation securite/audit

Priorite: P2
OWASP: A09 Security Logging and Monitoring Failures
ASVS L1: evenements de securite

Fichiers:

  • backend/routers/auth.py
  • backend/routers/devices.py
  • backend/routers/vlans.py
  • backend/routers/discovery.py

Les evenements sensibles ne sont pas journalises de maniere structuree: login reussi/echec, changement de compte, scans reseau, imports, suppressions. En cas d'incident, l'attribution et l'analyse seront limitees.

Recommandation: ajouter des logs structures sans secrets, incluant utilisateur, endpoint, action, resultat, compteurs et IP client issue du proxy de confiance.

SEC-13 - Integrite relationnelle SQLite non enforcee

Priorite: P2
OWASP: A04, A08
ASVS L1: integrite des donnees

Fichiers:

  • backend/database.py:6-9
  • backend/models.py
  • docs/data-model.md signale explicitement que les FK SQLite ne sont pas enforcees

Les ForeignKey SQLAlchemy ne suffisent pas sous SQLite sans PRAGMA foreign_keys=ON par connexion. L'application compense partiellement dans les routeurs, mais des incoherences restent possibles, par exemple via evolutions futures ou migrations.

Recommandation: activer le pragma via event SQLAlchemy, verifier les comportements de suppression, ajouter des tests d'integrite.

SEC-14 - Gestion des dependances et supply chain incomplete

Priorite: P2
OWASP: A06 Vulnerable and Outdated Components, A08 Software and Data Integrity Failures
ASVS L1: composants connus et maintenus

Fichiers:

  • backend/requirements.txt
  • frontend/package.json:9-19
  • frontend/package-lock.json
  • frontend/Dockerfile:3-6

Les dependances sont epinglees cote Python, mais il n'y a pas de workflow SCA documente. Cote frontend, package.json utilise des ranges ^, et le Dockerfile lance npm install au lieu de npm ci, ce qui reduit la reproductibilite. cytoscape reste declare alors que les notes disent que la topologie n'utilise plus Cytoscape.

Recommandation: utiliser npm ci, supprimer les dependances inutilisees, ajouter pip-audit/npm audit ou equivalent CI, documenter la procedure d'upgrade passlib/bcrypt.

SEC-15 - Import JSON sans schema ni limite de taille

Priorite: P2
OWASP: A04, A05
ASVS L1: validation des donnees importees

Fichiers:

  • frontend/src/App.vue:191-208
  • routes CRUD backend appelees ensuite

L'import JSON parse tout le fichier cote navigateur, puis cree des VLANs/devices en boucle sans validation de schema dedie ni limite de taille/quantite. Les erreurs de creation sont ignorees avec .catch(() => {}), ce qui peut masquer des imports partiels ou incoherents.

Recommandation: definir un schema d'import, limiter taille et nombre d'elements, afficher un bilan d'erreurs, et s'appuyer sur les validations serveur renforcees.

SEC-16 - v-html inutile dans la navigation

Priorite: P3
OWASP: A03 XSS, defense in depth
ASVS L1: sortie encodee

Fichiers:

  • frontend/src/App.vue:24
  • frontend/src/App.vue tableau tabs

v-html est utilise pour afficher des icones de navigation depuis des constantes locales. Le risque actuel est faible car les valeurs ne viennent pas de l'utilisateur, mais l'usage de HTML injecte est inutile et peut devenir dangereux lors d'une future evolution.

Recommandation: remplacer par du texte, des composants d'icones ou une interpolation normale.

SEC-17 - Endpoint health public minimal

Priorite: P3
OWASP: A05
ASVS L1: exposition minimale

Fichiers:

  • backend/main.py:111-113
  • docs/backend.md

GET /api/health est public. Dans un modele local derriere reverse-proxy, c'est acceptable pour un healthcheck minimal. Il ne divulgue que {"status":"ok"}; risque faible. Il doit simplement rester minimal et ne pas exposer version, configuration ou details d'erreur.

Recommandation: conserver public si necessaire au healthcheck, documenter cette decision, et s'assurer que le reverse-proxy ne l'expose pas avec plus d'information que necessaire.

SEC-18 - Documentation de securite et configuration d'environnement incompletes

Priorite: P2
OWASP: A05, A04
ASVS L1: configuration securisee documentee

Fichiers:

  • README.md absent
  • aucun .env.example
  • CLAUDE.md, AGENTS.md, docs/*.md

Les fichiers de notes de developpement sont riches, mais il manque un README utilisateur et un .env.example pour documenter SECRET_KEY, origines CORS si conservees, URL publique du reverse-proxy, modele TLS delegue, creation du compte initial et durcissement production/local.

Recommandation: ajouter une documentation securite minimale et un exemple d'environnement sans secrets reels.

Ecarts avec CLAUDE.md / AGENTS.md

  • README.md est absent alors que le perimetre demande sa revue. Les informations equivalentes sont dans CLAUDE.md, AGENTS.md et docs/.
  • Aucun .env.example n'est present, alors que SECRET_KEY est supporte par le code.
  • CLAUDE.md et AGENTS.md documentent le compte de bootstrap admin/admin; ce n'est pas un ecart fonctionnel, mais cela doit etre accompagne d'un changement force ou d'une consigne de non-exposition avant changement pour satisfaire une posture ASVS L1.
  • docs/backend.md indique que CORS ouvert est intentionnel pour un outil LAN. Avec un backend interne derriere reverse-proxy, le risque est reduit; il faut surtout documenter que le backend ne doit pas etre publie directement.
  • AGENTS.md indique que les routeurs sont proteges; c'est vrai pour les routeurs metier. GET /api/health reste public et doit rester explicitement assume.
  • Les notes disent "no Cytoscape", mais frontend/package.json garde cytoscape dans les dependances.

Couverture OWASP Top 10

Categorie Evaluation
A01 Broken Access Control Pas de multi-utilisateur/role. Routeurs metier proteges. Risque residuel faible a moyen via compte compromis.
A02 Cryptographic Failures JWT en localStorage, TLS delegue au reverse-proxy a documenter, secret en bind mount lisible localement.
A03 Injection SQL/commande correctement limites; validation URL/CSS/strings a renforcer; v-html inutile.
A04 Insecure Design Bootstrap admin/admin a encadrer, scan reseau authentifie, absence de politique session/password.
A05 Security Misconfiguration CORS permissif pour API interne, headers HTTP a porter par le proxy ou Nginx embarque, conteneurs peu durcis, health public minimal.
A06 Vulnerable and Outdated Components Pas de SCA ni politique d'upgrade; npm install; dependance inutilisee.
A07 Identification and Authentication Failures Bootstrap admin/admin sans changement force, pas de rate limit, politique password faible.
A08 Software and Data Integrity Failures Build frontend non reproductible strictement; FK SQLite non enforcees; imports JSON peu controles.
A09 Logging and Monitoring Failures Logs securite absents.
A10 SSRF Discovery permet ping/DNS vers cibles fournies par utilisateur authentifie.

References