|
5 | 5 |
|
6 | 6 | const fs = require('fs'); |
7 | 7 | const path = require('path'); |
| 8 | +const { readFileWithLimit } = require('../utils/fs-safe'); |
8 | 9 |
|
9 | 10 | /** |
10 | 11 | * Default suppression config |
@@ -34,20 +35,18 @@ function loadConfig(projectRoot) { |
34 | 35 | ]; |
35 | 36 |
|
36 | 37 | for (const configPath of configPaths) { |
37 | | - if (fs.existsSync(configPath)) { |
38 | | - try { |
39 | | - // Check file size before reading to prevent DoS |
40 | | - const stats = fs.statSync(configPath); |
41 | | - if (stats.size > MAX_CONFIG_SIZE) { |
42 | | - console.error(`[WARN] Config file too large (${stats.size} bytes), using defaults`); |
43 | | - continue; |
44 | | - } |
45 | | - const content = fs.readFileSync(configPath, 'utf8'); |
46 | | - const userConfig = JSON.parse(content); |
47 | | - return mergeConfig(DEFAULT_CONFIG, userConfig); |
48 | | - } catch (err) { |
49 | | - // Invalid config, use defaults |
| 38 | + try { |
| 39 | + // Open once and read through the fd (size check + read on the same |
| 40 | + // inode) to avoid an exists/stat/read TOCTOU. A missing file throws |
| 41 | + // ENOENT and falls through to the next candidate / defaults. |
| 42 | + const content = readFileWithLimit(configPath, MAX_CONFIG_SIZE); |
| 43 | + const userConfig = JSON.parse(content); |
| 44 | + return mergeConfig(DEFAULT_CONFIG, userConfig); |
| 45 | + } catch (err) { |
| 46 | + if (err && err.code === 'EFBIG') { |
| 47 | + console.error('[WARN] Config file too large, using defaults'); |
50 | 48 | } |
| 49 | + // Missing or invalid config: try the next candidate / defaults. |
51 | 50 | } |
52 | 51 | } |
53 | 52 |
|
|
0 commit comments