From 10af5a50e5e7425409b29b66ed0d79602ccdae1a Mon Sep 17 00:00:00 2001 From: root Date: Tue, 23 Jun 2026 10:18:08 +0200 Subject: [PATCH] feature: migration dvf en TypeScript parse.js/sync.js/lib/dotenv.js -> .ts (run via tsx), ajout tsconfig.json, eslint config TS-enabled (parser tseslint + resolver typescript), devDeps typescript/typescript-eslint/tsx/eslint-import-resolver-typescript, scripts package.json en tsx + typecheck, .lintstagedrc *.ts, README en tsx. Fix securite: escapeId sur les identifiants de colonnes dans sync.ts (au lieu de backticks bruts) pour ne pas casser/injecter via un en-tete CSV distant. Suppression du todo.txt audit (finding escapeId traite). Co-Authored-By: Claude Opus 4.8 (1M context) --- .lintstagedrc | 1 + README.md | 6 +- eslint.config.js | 37 +- lib/{dotenv.js => dotenv.ts} | 0 package-lock.json | 844 ++++++++++++++++++++++++++++++++++- package.json | 15 +- parse.js => parse.ts | 17 +- sync.js => sync.ts | 29 +- todo.txt | 39 -- tsconfig.json | 23 + 10 files changed, 941 insertions(+), 70 deletions(-) rename lib/{dotenv.js => dotenv.ts} (100%) rename parse.js => parse.ts (83%) rename sync.js => sync.ts (85%) delete mode 100644 todo.txt create mode 100644 tsconfig.json diff --git a/.lintstagedrc b/.lintstagedrc index d239083..326ac1a 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,5 +1,6 @@ { "*.js": ["eslint --fix", "prettier --write"], + "*.ts": ["eslint --fix", "prettier --write"], "*.css": ["prettier --write"], "*.jsx": ["prettier --write"], "*.html.twig": ["twig-cs-fixer lint --fix"], diff --git a/README.md b/README.md index 361d92b..baa78d1 100644 --- a/README.md +++ b/README.md @@ -36,14 +36,14 @@ Voila un sample des données parsées [geodvf/sample.json](geodvf/sample.json) Détecte les années manquantes en base, télécharge les csv.gz si besoin, parse et insère directement. - node sync.js # importe les années manquantes - node sync.js 2025 # force le re-import d'une année + tsx sync.ts # importe les années manquantes + tsx sync.ts 2025 # force le re-import d'une année # parse (manuel) Génère du SQL sur stdout à partir d'un csv.gz (ancien workflow). - node parse.js geodvf/2025.csv.gz | gzip > geodvf/2025.sql.gz + tsx parse.ts geodvf/2025.csv.gz | gzip > geodvf/2025.sql.gz pv geodvf/2025.sql.gz | gunzip | mysql -u user -ppassword -h host database ``` diff --git a/eslint.config.js b/eslint.config.js index a299df1..d1d1c83 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,6 +5,7 @@ import n from 'eslint-plugin-n'; import raflint from 'eslint-plugin-raflint'; import pluginimport from 'eslint-plugin-import-x'; import babelParser from '@babel/eslint-parser'; +import tseslint from 'typescript-eslint'; import js from '@eslint/js'; import globals from 'globals'; @@ -31,6 +32,7 @@ export default [ ignores: ['legacy/', 'dist/', 'node_modules/', 'web/', 'public/', 'coverage/', 'data/', 'vendor/', 'react/', 'js/', 'assets/', '**/old/'], }, { + files: ['**/*.{js,mjs,cjs,ts}'], languageOptions: { globals: { ...globals.browser, @@ -47,8 +49,19 @@ export default [ plugins: { raflint, }, + settings: { + 'import-x/resolver': { + typescript: { + alwaysTryTypes: true, + noWarnOnMultipleProjects: true, + project: ['tsconfig.json'], + }, + node: true, + }, + }, rules: { 'sonarjs/no-empty-function': 0, + 'unicorn/no-this-outside-of-class': 0, 'sonarjs/no-unused-expressions': 0, 'sonarjs/no-unsafe-unzip': 0, 'sonarjs/os-command': 0, @@ -133,6 +146,7 @@ export default [ 'unicorn/prefer-ternary': 0, 'unicorn/prefer-top-level-await': 0, 'unicorn/prevent-abbreviations': 0, + 'unicorn/name-replacements': 0, 'unicorn/consistent-compound-words': 0, // ne pas retirer `undefined` passe en argument (souvent requis par la signature -> casse tsc) 'unicorn/no-useless-undefined': ['error', { checkArguments: false }], @@ -218,7 +232,6 @@ export default [ 'wrap-iife': ['error', 'inside'], yoda: 'error', 'vars-on-top': 'error', - radix: ['error', 'as-needed'], 'prefer-regex-literals': 'error', 'prefer-promise-reject-errors': 'error', 'promise/catch-or-return': ['error', { terminationMethod: ['catch', 'finally'] }], @@ -285,4 +298,26 @@ export default [ 'prefer-const': ['error', { destructuring: 'all' }], }, }, + { + files: ['**/*.ts'], + languageOptions: { + parser: tseslint.parser, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + // le compilo TS gère les globals/types : évite les faux positifs no-undef (Express, RequestInit) + 'no-undef': 'off', + // la règle core ne comprend pas `import type` + 'no-duplicate-imports': 'off', + }, + }, + { + // fichiers de déclaration générés (vite-env.d.ts) : nom imposé par la convention Vite + files: ['**/*.d.ts'], + rules: { + 'unicorn/filename-case': 'off', + }, + }, ]; diff --git a/lib/dotenv.js b/lib/dotenv.ts similarity index 100% rename from lib/dotenv.js rename to lib/dotenv.ts diff --git a/package-lock.json b/package-lock.json index 4483506..c286dea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@babel/eslint-parser": "^8.0.1", "@eslint/js": "^10.0.1", "eslint": "^10.5.0", + "eslint-import-resolver-typescript": "^4.4.5", "eslint-plugin-import-x": "^4.16.2", "eslint-plugin-n": "^18.1.0", "eslint-plugin-promise": "^7.3.0", @@ -29,7 +30,10 @@ "husky": "^9.1.7", "lint-staged": "^17.0.7", "prettier": "^3.8.4", - "ts-api-utils": "^2.5.0" + "ts-api-utils": "^2.5.0", + "tsx": "^4.22.4", + "typescript": "^6.0.3", + "typescript-eslint": "^8.61.1" }, "engines": { "node": ">=21.0.0" @@ -297,6 +301,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -612,6 +1058,152 @@ "undici-types": ">=7.24.0 <7.24.7" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/types": { "version": "8.61.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", @@ -626,6 +1218,76 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", @@ -1392,6 +2054,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1515,6 +2219,41 @@ } } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz", + "integrity": "sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, "node_modules/eslint-plugin-es-x": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", @@ -1929,6 +2668,24 @@ "dev": true, "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -1992,6 +2749,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -2189,6 +2961,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3030,6 +3812,23 @@ "node": ">=18" } }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -3051,6 +3850,25 @@ "license": "0BSD", "optional": true }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3078,6 +3896,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.1.tgz", + "integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.61.1", + "@typescript-eslint/parser": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/undici-types": { "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", diff --git a/package.json b/package.json index 7abc36c..2e3b834 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "license": "ISC", "author": "Raphael Piccolo", "type": "module", - "main": "server.js", + "main": "sync.ts", "scripts": { - "cov": "c8 npm run test", "prepare": "husky", - "start": "node server.js" + "start": "tsx sync.ts", + "typecheck": "tsc -b" }, "dependencies": { "csv-parse": "^7.0.0", @@ -27,6 +27,7 @@ "@babel/eslint-parser": "^8.0.1", "@eslint/js": "^10.0.1", "eslint": "^10.5.0", + "eslint-import-resolver-typescript": "^4.4.5", "eslint-plugin-import-x": "^4.16.2", "eslint-plugin-n": "^18.1.0", "eslint-plugin-promise": "^7.3.0", @@ -37,12 +38,16 @@ "husky": "^9.1.7", "lint-staged": "^17.0.7", "prettier": "^3.8.4", - "ts-api-utils": "^2.5.0" + "ts-api-utils": "^2.5.0", + "tsx": "^4.22.4", + "typescript": "^6.0.3", + "typescript-eslint": "^8.61.1" }, "engines": { "node": ">=21.0.0" }, "allowScripts": { - "unrs-resolver": true + "unrs-resolver": true, + "esbuild@0.28.1": true } } diff --git a/parse.js b/parse.ts similarity index 83% rename from parse.js rename to parse.ts index 1bb1977..1cef7ba 100644 --- a/parse.js +++ b/parse.ts @@ -2,7 +2,7 @@ import { parse } from 'csv-parse'; import fs from 'node:fs'; import zlib from 'node:zlib'; import mysql from 'mysql2'; -import './lib/dotenv.js'; +import './lib/dotenv.ts'; if (process.argv.length != 3) { throw new Error('You should give a project dir'); @@ -25,8 +25,8 @@ if (!connectionString) { const connection = mysql.createConnection(process.env.MYSQL); // generate sql -let columns = []; -const flushBatch = (batch) => { +const columns: string[] = []; +const flushBatch = (batch: unknown[][]) => { if (batch.length === 0) return; const values = batch.map(row => `(${row.map(val => connection.escape(val)).join(', ')})`).join(', '); @@ -37,18 +37,19 @@ const flushBatch = (batch) => { // quand quelques ligne de csv sont parsées on les assemble puis on genere le sql parser.on('readable', () =>{ - let record = null; - const batch = []; - - while ((record = parser.read()) !== null) { + const batch = []; + + let record = parser.read(); + while (record !== null) { // console.log(record); // get columns and values to insert and escape them for sql if (columns.length === 0) { - columns = Object.keys(record).map(col => connection.escapeId(col)); + columns.push(...Object.keys(record).map(col => connection.escapeId(col))); } const values = Object.values(record); batch.push(values); + record = parser.read(); } flushBatch(batch); diff --git a/sync.js b/sync.ts similarity index 85% rename from sync.js rename to sync.ts index 145a056..8ec2939 100644 --- a/sync.js +++ b/sync.ts @@ -4,21 +4,22 @@ import zlib from 'node:zlib'; import { pipeline } from 'node:stream/promises'; import { Writable } from 'node:stream'; import mysql from 'mysql2/promise'; -import './lib/dotenv.js'; - -const BASE_URL = 'https://files.data.gouv.fr/geo-dvf/latest/csv'; -const GEODVF_DIR = 'geodvf'; +import type { Connection } from 'mysql2/promise'; +import './lib/dotenv.ts'; const connectionString = process.env.MYSQL; if (!connectionString) { throw new Error('MYSQL environment variable not set'); } +const BASE_URL = 'https://files.data.gouv.fr/geo-dvf/latest/csv'; +const GEODVF_DIR = 'geodvf'; + // parse mysql connection string const url = new URL(connectionString); const dbConfig = { host: url.hostname, - port: url.port || 3306, + port: url.port ? Number(url.port) : 3306, user: url.username, password: decodeURIComponent(url.password), database: url.pathname.slice(1), @@ -29,12 +30,12 @@ async function getYearsOnServer() { const res = await fetch(`${BASE_URL}/`); const html = await res.text(); const years = Array.from(html.matchAll(/href="(\d{4})\/"/g), match => Number(match[1])); - return years.toSorted(); + return years.toSorted((a, b) => a - b); } -async function getYearsInDb(connection) { +async function getYearsInDb(connection: Connection) { const [rows] = await connection.query('SELECT DISTINCT YEAR(date_mutation) as annee FROM dvf ORDER BY annee'); - return rows.map(r => r.annee); + return (rows as { annee: number }[]).map(r => r.annee); } async function downloadYear(year) { @@ -55,13 +56,13 @@ async function downloadYear(year) { return file; } -async function importYear(connection, year, file) { +async function importYear(connection: Connection, year: number, file: string) { console.log(` Parsing et insertion de ${file} ...`); - let columns = null; + let columns: string[] | null = null; let inserted = 0; const BATCH_SIZE = 5000; - let batch = []; + let batch: unknown[][] = []; const flush = async () => { if (batch.length === 0) return; @@ -80,10 +81,12 @@ async function importYear(connection, year, file) { const writer = new Writable({ objectMode: true, - async write(record, _encoding, callback) { + async write(record: Record, _encoding, callback) { try { if (!columns) { - columns = Object.keys(record).map(col => `\`${col}\``); + // escapeId (et non un simple backtick) pour ne pas casser l'identifiant + // si un en-tête CSV distant contient un backtick — cf. parse.ts + columns = Object.keys(record).map(col => connection.escapeId(col)); } const values = Object.values(record).map(v => (v === '' ? null : v)); batch.push(values); diff --git a/todo.txt b/todo.txt deleted file mode 100644 index 37e5d0f..0000000 --- a/todo.txt +++ /dev/null @@ -1,39 +0,0 @@ -# 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). - -## 🔴 CRITIQUE (exploitable à distance / fuite de données / RCE) -RAS - -## 🟠 ÉLEVÉ -RAS - -## 🟡 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. - -## 🔵 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. diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4e69808 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2024", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "allowJs": true, + "checkJs": false, + "noEmit": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": false, + "isolatedModules": true, + "verbatimModuleSyntax": false, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": false, + "noImplicitAny": false, + "strictNullChecks": false, + "forceConsistentCasingInFileNames": true + }, + "include": ["*.ts", "lib/**/*.ts"], + "exclude": ["node_modules", "dvf", "geodvf", "coverage", "dist"] +}