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

7.1 KiB
Raw Blame History

Security Fixes Applied — Phase 3

Date: 2026-05-06

SEC-FIX-006 — Validation des entrées discovery

Problème

  • dns_server acceptait n'importe quelle chaîne (risque d'injection dans le résolveur DNS)
  • /api/discovery/ping acceptait n'importe quelle chaîne comme IP (transmise au sous-processus ping)
  • Plusieurs targets pouvaient contourner le cap de 1024 : 5 × /22 = 5110 hôtes

Corrections appliquées

backend/routers/discovery.py

  • ScanRequest.dns_server : field_validator qui appelle ipaddress.ip_address(v) — rejette toute valeur non-IP (422)
  • PingRequest.ips : field_validator qui valide chaque IP — rejette toute entrée malformée avant d'appeler le sous-processus (422)
  • MAX_HOSTS_TOTAL = 4096 : cap global sur la somme des hôtes de tous les targets — rejette si dépassé (400)

SEC-FIX-007 — Validation Pydantic métier

Problème

Aucun validator sur les schémas métier : valeurs hors-domaine, CIDR invalides, couleurs arbitraires, URLs arbitraires, IPs invalides, types inconnus acceptés silencieusement.

Corrections appliquées

backend/routers/vlans.py

  • vlan_id : 14094 (norme 802.1Q)
  • name : non vide, max 100 caractères (strip)
  • cidr : ipaddress.ip_network(strict=False) si non vide
  • color : regex ^#[0-9a-fA-F]{6}$

backend/routers/devices.py

  • name : non vide, max 100 caractères (strip)
  • description : max 500 caractères
  • type : enum des 18 types valides
  • virt_type : enum {null, baremetal, lxc, qemu}
  • url : urlparse — schéma http/https + netloc non vide
  • InterfaceCreate.name : non vide, max 50 caractères
  • InterfaceCreate.ip_address : ipaddress.ip_address(v) si non vide

Toutes les erreurs de validation retournent 422 (comportement standard FastAPI/Pydantic).


SEC-FIX-008 — En-têtes HTTP sécurité Nginx

Problème

Aucun en-tête de sécurité HTTP : pas de CSP, pas de protection contre le MIME sniffing ou le framing.

Corrections appliquées

frontend/nginx.conf

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self';

Tous les en-têtes utilisent le flag always pour s'appliquer aussi aux réponses d'erreur.


SEC-FIX-009 — Documentation TLS/HTTPS

Statut : CORRIGÉ EN PHASE 2/3

Documentation ajoutée au README.md lors de la session précédente :

  • Section HTTPS avec exemple nginx reverse-proxy complet
  • docker-compose.override.yml pour limiter l'exposition au loopback

SEC-FIX-012 — Logs d'audit structurés

Problème

Aucune trace des événements d'authentification : connexions, échecs, changements de mot de passe.

Corrections appliquées

backend/routers/auth.py

  • Logger stdlib logging.getLogger("audit") — aucune dépendance supplémentaire
  • Helper _log_audit(event, **kw) : émet une ligne JSON {"event": ..., "ts": ..., ...}
  • Événements loggés :
    • auth.login.success — username, ip
    • auth.login.failure — username, ip
    • auth.login.rate_limited — ip, username (si disponible), reason (ip | username)
    • auth.token_rejected — username, reason (user_not_found | version_mismatch)
    • auth.account.password_changed — username
    • auth.account.username_changed — old_username, new_username
    • auth.account.bad_password — username

Activation dans Docker via la config de logging uvicorn (stdout par défaut).


SEC-FIX-013 — PRAGMA foreign_keys=ON

Problème

SQLite désactive les contraintes de clés étrangères par défaut. Les suppressions en cascade ne sont pas enforced.

Corrections appliquées

backend/database.py

@event.listens_for(Engine, "connect")
def _set_sqlite_pragma(dbapi_conn, _record):
    if isinstance(dbapi_conn, sqlite3.Connection):
        cursor = dbapi_conn.cursor()
        cursor.execute("PRAGMA foreign_keys=ON")
        cursor.close()

Le listener s'exécute sur chaque nouvelle connexion SQLite, y compris les connexions de test.


SEC-FIX-014 — Dépendance cytoscape non utilisée

Statut : CORRIGÉ EN PHASE 2

cytoscape supprimé de frontend/package.json lors de la session précédente.


SEC-FIX-015 — Import JSON sans limite de taille ni validation de schéma

Problème

Un fichier JSON de plusieurs gigaoctets pouvait être chargé en mémoire. Aucune validation du schéma avant traitement.

Corrections appliquées

frontend/src/App.vue

  • Vérification file.size > 5 * 1024 * 1024 avant file.text() — rejette avec message localisé
  • Validation de schéma :
    • Le JSON doit être un objet (pas un tableau, pas null)
    • vlans et devices, si présents, doivent être des tableaux

frontend/src/i18n.js

  • Clé importTooLarge ajoutée (fr, en, es)

SEC-FIX-016 — v-html dans App.vue

Problème

v-html utilisé pour injecter des entités HTML (&#9632;, &#9670;, &#9635;) dans les boutons de navigation. Risque XSS si la source venait à être dynamique.

Corrections appliquées

frontend/src/App.vue

  • Entités HTML remplacées par leurs équivalents Unicode directs (, , )
  • v-html="tab.icon" remplacé par {{ tab.icon }} (interpolation texte — échappe automatiquement)

Problème

La vue Liens avait été retirée du frontend en phase 2/3, mais le backend conservait :

  • backend/routers/links.py — router toujours enregistré dans main.py
  • class Link dans models.py — ORM orphelin
  • Référence explicite à models.Link dans delete_device (devices.py)
  • La table links en base — avec FK vers devices, ce qui bloquerait les suppressions d'équipements avec PRAGMA foreign_keys=ON

Corrections appliquées

backend/routers/links.py — Supprimé

backend/models.py — Classe Link supprimée

backend/routers/devices.py — Suppression de la requête db.query(models.Link).filter(...).delete() dans delete_device

backend/main.py

  • Import links retiré
  • app.include_router(links.router, ...) retiré
  • Nouvelle migration _migrate_drop_links_table() : DROP TABLE links si elle existe (avec PRAGMA foreign_keys=OFF/ON pour éviter les erreurs FK pendant la migration)

Fichiers modifiés

Fichier Fix(es)
backend/database.py SEC-FIX-013
backend/models.py SEC-FIX-017
backend/main.py SEC-FIX-017
backend/routers/auth.py SEC-FIX-012
backend/routers/vlans.py SEC-FIX-007
backend/routers/devices.py SEC-FIX-007, SEC-FIX-017
backend/routers/discovery.py SEC-FIX-006
backend/routers/links.py SEC-FIX-017 (supprimé)
backend/tests/test_validation.py Tests SEC-FIX-006, 007, 013, 017 (nouveau)
frontend/nginx.conf SEC-FIX-008
frontend/src/App.vue SEC-FIX-015, SEC-FIX-016
frontend/src/i18n.js SEC-FIX-015