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

109 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Security Decisions — Phase 3
Date: 2026-05-06
Ce document enregistre les arbitrages pris lors de la phase 3, les alternatives écartées et les limitations acceptées.
---
## SEC-FIX-006 — Cap global discovery
**Décision** : `MAX_HOSTS_TOTAL = 4096` (4 × /22).
**Alternatives écartées** :
- Cap par nombre de targets : ne bloque pas 1 seul /11 découpé en CIDRs /22.
- Cap plus bas (1024 global) : trop restrictif pour les déploiements multi-VLAN légitimes.
**Limitation** : le cap s'applique à la requête, pas à l'utilisateur ou à la session. Un utilisateur authentifié peut lancer N requêtes consécutives. Mitigation acceptable : l'endpoint est protégé par authentification et nécessite `CAP_NET_RAW`.
---
## SEC-FIX-007 — Validation Pydantic : périmètre choisi
**Décision** : validateurs sur les champs à domaine fini (type, virt_type, couleur, CIDR, IP, URL). Pas de validation sur `description` au-delà d'une longueur max.
**Alternatives écartées** :
- Liste noire de caractères dans `name`/`description` : SQLAlchemy paramétrise toutes les requêtes → pas de risque SQL injection. La liste noire ajouterait une friction sans gain de sécurité.
- Validation de `vlan_id` contre les VLANs existants dans l'interface (unicité déjà enforced en base).
**Limite acceptée** : `name` peut contenir des caractères Unicode arbitraires — ce n'est pas un vecteur d'injection dans ce contexte (pas de rendu HTML côté serveur, pas de shell).
---
## SEC-FIX-008 — Content-Security-Policy
**Décision** : `style-src 'self' 'unsafe-inline'` conservé.
**Pourquoi** : Vue 3 utilise des attributs `style=""` inline (ex: `style="display:none"` pour `v-show`, styles dynamiques sur les chips de couleur VLAN). Ces attributs sont contrôlés par `style-src`. Supprimer `'unsafe-inline'` casserait l'app sans migration vers CSS classes ou nonces.
**Alternative écartée** : nonces CSP pour les styles inline — Vite ne génère pas de nonces en production, nécessiterait un middleware serveur dynamique incompatible avec Nginx statique.
**Décision** : `frame-ancestors 'none'` + `X-Frame-Options: DENY` — redondant mais nécessaire pour les navigateurs qui n'implémentent pas CSP `frame-ancestors`.
---
## SEC-FIX-012 — Logs d'audit : format et activation
**Décision** : JSON one-line via `logging.getLogger("audit")`, stdout.
**Pourquoi** : stdlib uniquement, pas de dépendance (structlog, loguru écartes). Docker collecte stdout. Le format JSON permet le parsing par Loki/ELK sans transformation.
**Activation** : uvicorn hérite de la config logging Python. Pour filtrer uniquement les événements d'audit : `--log-config` ou configuration Python logging dans main.py (non ajouté : hors périmètre de cette phase).
**Limitation** : les logs ne sont pas persistants entre redémarrages sans volume dédié ou système de log externe. Acceptable pour un déploiement self-hosted.
---
## SEC-FIX-013 — PRAGMA foreign_keys=ON
**Décision** : listener sur l'événement `connect` de SQLAlchemy — s'applique à toutes les connexions, y compris les connexions de test.
**Attention** : la migration `_migrate_vlan_nullable()` dans `main.py` désactive temporairement `PRAGMA foreign_keys=OFF` pour recréer la table vlans. Elle le réactive ensuite. Ce pattern est correct car le listener ne s'applique qu'à la connexion DBAPI sous-jacente, pas aux connexions SQLAlchemy qui wrappent.
**Conséquence sur SEC-FIX-017** : avec `foreign_keys=ON`, la table `links` (qui référençait `devices`) aurait bloqué les `DELETE` sur les équipements. C'est la raison pour laquelle `_migrate_drop_links_table()` est exécutée au démarrage.
---
## SEC-FIX-015 — Limite import JSON côté frontend uniquement
**Décision** : limite de taille appliquée uniquement côté frontend (JavaScript, avant `file.text()`).
**Limitation connue** : cette protection est bypassable par un appel direct à l'API. Cependant :
- L'endpoint d'import JSON (`/api/devices/`, `/api/vlans/`) est protégé par authentification.
- FastAPI/uvicorn a une limite de corps par défaut (1 Mo via Starlette). Pour les requêtes individuelles, la validation Pydantic rejette les données invalides.
- La protection frontend couvre 99% des cas d'usage (import via UI).
**Alternative écartée** : limite de taille côté Nginx (`client_max_body_size`) — le scan de découverte et l'import sont deux flux différents ; limiter globalement pourrait bloquer des scans légitimes. Une limite fine par endpoint nécessiterait une configuration Nginx complexe.
---
## SEC-FIX-016 — v-html
**Décision** : les icônes de navigation (■ ◆ ▣) sont des caractères Unicode hardcodés dans le `computed`. Pas de source externe, pas de traduction, pas d'interpolation.
**Risque résiduel** : nul — les caractères Unicode passent par `{{ }}` qui échappe automatiquement.
---
## SEC-FIX-017 — Table `links` : DROP vs conservation
**Décision** : DROP de la table `links` via `_migrate_drop_links_table()` au démarrage.
**Pourquoi** : avec `PRAGMA foreign_keys=ON` (SEC-FIX-013), la table `links` (FK → `devices.id` sans ON DELETE CASCADE) aurait bloqué toute suppression d'équipement ayant des liens. Conserver la table sans le code applicatif pour la maintenir est une dette technique et un risque opérationnel.
**Données perdues** : les liens existants en base sont supprimés de façon irréversible. Acceptable car la fonctionnalité est retirée de l'interface — les données ne sont plus accessibles ni maintenables.
**Alternative écartée** : ajouter ON DELETE CASCADE sur la table existante. Aurait préservé les données mais complexifié la migration SQLite (pas d'ALTER CONSTRAINT) et laissé une table orpheline indéfiniment.
---
## Points restants non traités en Phase 3
| ID | Raison du report |
|----|-----------------|
| SEC-FIX-009 | Couvert : documentation TLS/HTTPS dans README (phases 2/3) |
| SEC-FIX-014 | Couvert : cytoscape supprimé en phase 2/3 |
| Audit log configuration fine | Hors périmètre — nécessite une décision d'infrastructure (Loki, ELK, etc.) |
| `no-new-privileges` backend | Contrainte technique inhérente à `cap_net_raw=ep` pour ping (documenté) |
| Rate limiting multi-worker | Mitigation en place via Nginx `limit_req` |