diff --git a/package.json b/package.json index 3fa8eef..7abc36c 100644 --- a/package.json +++ b/package.json @@ -41,5 +41,8 @@ }, "engines": { "node": ">=21.0.0" + }, + "allowScripts": { + "unrs-resolver": true } } diff --git a/todo.txt b/todo.txt index 00115e1..37e5d0f 100644 --- a/todo.txt +++ b/todo.txt @@ -1,96 +1,39 @@ -REVIEW DE CODE — 2026-04-26 -============================================ +# AUDIT SÉCURITÉ — dvf — 2026-06-21 +# Stack: service Node (CLI one-shot d'import — pas de serveur HTTP, pas de controller, pas de service Docker) +# Méthode: revue de code statique. Findings réels uniquement (conventions stack acceptées exclues). +# Note: outil CLI lancé à la main (sync.js / parse.js). Pas de Dockerfile, pas de .drone.yml, +# absent du docker-compose.yml racine => aucune surface réseau exposée. +# .env présent sur disque MAIS git-ignored (.gitignore: .env + .env*) et NON git-tracké +# (git ls-files .env => vide). Donc pas de secret committé. Le .env contient néanmoins +# le mot de passe root MySQL de la prod flatbay.fr (mysql://root:...@flatbay.fr/dvf). -Outil CLI one-shot pour synchroniser la BDD `dvf` (Demandes de -Valeurs Foncieres, donnees publiques data.gouv.fr) avec une table -MySQL. 2 scripts (parse.js 70 lignes, sync.js 155 lignes) + lib/ -dotenv. Pas de service Docker, lance manuellement. +## 🔴 CRITIQUE (exploitable à distance / fuite de données / RCE) +RAS -Le dossier contient ~4 Go de data CSV/GZ telechargees (gitignored). -Probablement utilise par flatbay pour les estimations immobilieres. +## 🟠 ÉLEVÉ +RAS -SECURITE --------- -[ ] sync.js : fetch sans verification d'integrite - sync.js:51 : `const res = await fetch(url)`. Pas de check du - sha256 du fichier. data.gouv.fr est trusted, mais MITM ou - DNS hijack pourrait empoisonner les imports. Risque - theorique pour une source publique. +## 🟡 MOYEN +[ ] Identifiant de colonne non échappé dans l'INSERT (injection SQL via en-tête CSV) — sync.js:69 (+85-87) + Risque: `columns` vient des clés du CSV distant, simplement entouré de backticks + (`\`${col}\``) au lieu de `connection.escapeId(col)`. Un nom de colonne contenant un + backtick casse l'identifiant et permettrait d'injecter du SQL dans `INSERT INTO dvf (...)`. + Les VALEURS sont en placeholders `?` (sûres) — seul l'en-tête est concerné. Source = + data.gouv.fr (de confiance), import CLI non exposé : exploitabilité réelle faible, mais + le projet possède déjà la bonne primitive (parse.js:48 utilise escapeId). À aligner. + Reco: `columns = Object.keys(record).map(col => connection.escapeId(col))` dans sync.js, + et valider que les colonnes attendues correspondent au schéma `dvf` (whitelist) avant insert. -BUGS / FRAGILITE ----------------- -[ ] parse.js : code mort — INSERT commente - parse.js:37-40 : - // connection.query(sql, (error, results) => { - // if (error) throw error; - // console.log('Inserted rows:', results.affectedRows); - // }); - Le script ne fait que `console.log(sql)`. Donc parse.js ne - fonctionne PAS, il print juste le SQL. Soit reactiver le - query, soit delete tout le fichier (sync.js le remplace). - -[ ] parse.js et sync.js : 2 scripts qui font le meme job - parse.js : ancienne version pour CSV brut DVF. Charge un seul - fichier en parametre, gere CSV avec delimiteur `|` ou `,` selon - extension. - sync.js : version moderne pour geodvf, telecharge auto + - insertion batch via prepared statements. - A consolider en un seul script. - -[ ] sync.js : pas de reprise sur erreur - sync.js:43-58 (downloadYear) : si la connexion casse au milieu - d'un download de plusieurs Go, le fichier .gz est partiel mais - `fs.existsSync` ligne 45 retourne true => skip le re-download. - Erreur silencieuse au parse. A check la taille ou utiliser - `.tmp` puis rename atomique. - -[ ] sync.js : pas de DELETE/REPLACE pour les annees deja en BDD - sync.js:131 : `missing = serverYears.filter(y => !dbYears. - includes(y))`. Ne re-importe pas. Mais si les donnees d'une - annee sont mises a jour cote source (ex: 2024 corrige), pas - de mecanisme pour rafraichir. A faire un mode `--force` ou - upsert. - -[ ] sync.js : forceYear traite seulement - sync.js:131-135 : `forceYear` ecrase le filter des manquantes, - importe une annee specifique. Mais ne supprime pas les rows - existantes => duplicates en BDD. Bug si execute 2 fois pour - la meme annee. - -[ ] sync.js : `process.argv[2]` pour forceYear - sync.js:119. Pas de yargs ou argument parser. Fragile (pas - d'aide, pas de validation). Cf scripts/assistant/release.js - dans check/ qui utilise yargs. - -[ ] sync.js : `inserted % 100_000 < BATCH_SIZE` modulo log progress - sync.js:76 : `if (inserted % 100_000 < BATCH_SIZE)`. Si BATCH_SIZE - ne divise pas exactement 100_000, le log peut etre saute ou - duplique. Edge case mineur. - -[ ] parse.js : fragile sur extensions multiples - parse.js:13 : `const delimiter = (file.match(/\\.gz$/)) ? ',' : - '|';`. Si le fichier est `2025.full.csv` (sans .gz), utilise - `|`. Si c'est un fichier geodvf .csv sans .gz, faux delimiteur. - A baser sur le contenu (autodetection). - -[ ] sync.js : connection.end() jamais en cas d'erreur - sync.js:139, 152 : end() appele sur les paths ok/done. Si une - exception throw entre-temps, connection reste ouverte. A - passer en try/finally. - -[ ] columns : Object.keys(record).map(col => `\`${col}\``) - sync.js:89. Si le nom de colonne contient un backtick (peu - probable cote data.gouv), escape cassé. A passer en - `connection.escapeId(col)` (utilise dans parse.js:53). - -CODE MORT / POLLUTION ---------------------- -[ ] README.md : 30+ lignes de curl manuels - Inutile depuis sync.js qui fait le download auto. A simplifier. - -CONVENTIONS ------------ -[ ] Mauvaise pratique : parse.js fait du concat avec escape - parse.js:32 : `batch.map(row => '(' + row.map(val => connection. - escape(val)).join(', ') + ')')`. Theoriquement OK avec escape, - mais prepared statements sont preferes. A migrer ou delete. +## 🔵 DURCISSEMENT (faible) +[ ] .env en clair contient le root MySQL de la prod flatbay.fr — .env:1 + Risque: `mysql://root:Y3458mo_gppp@flatbay.fr/dvf`. Non committé (bien), mais c'est le + compte root prod posé en clair sur le host de dev. Compromission du host = accès root BDD prod. + Reco: utiliser un user MySQL dédié `dvf` à privilèges restreints (INSERT/SELECT sur la base + `dvf` uniquement), pas root. Confirmer que ce mot de passe n'a jamais transité par un commit + (historique git du repo Gitea) et le faire tourner s'il a fuité ailleurs. +[ ] Téléchargement distant sans vérification d'intégrité — sync.js:48 / downloadYear + Risque: `fetch(url)` sur files.data.gouv.fr puis insertion directe en BDD prod, sans + contrôle de hash/taille. Source publique de confiance (URL en dur, pas d'input user => pas de + SSRF), mais un MITM/empoisonnement DNS empoisonnerait les données importées. Risque théorique. + Reco: vérifier un checksum publié si disponible, ou au minimum la taille/cohérence du fichier. +# Audit: pas de RCE/SSRF/command-injection/route exposée. Surface = un script d'import CLI.