From e7b5c62eb7d012778bb583fce9884b3a7ca4b3eb Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Thu, 12 Feb 2026 15:28:30 -0800 Subject: [PATCH] fix: use readFile utility in hooks and add pattern type safety - Replace raw fs.readFileSync with readFile() from utils in check-console-log.js and post-edit-console-warn.js to eliminate TOCTOU race conditions (file deleted between existsSync and read) - Remove redundant existsSync in post-edit-format.js (exec already handles missing files via its catch block) - Resolve path upfront in post-edit-typecheck.js before tsconfig walk - Add type guard in getGitModifiedFiles() to skip non-string and empty patterns before regex compilation --- scripts/hooks/check-console-log.js | 6 +++--- scripts/hooks/post-edit-console-warn.js | 6 ++++-- scripts/hooks/post-edit-format.js | 4 ++-- scripts/hooks/post-edit-typecheck.js | 6 ++++-- scripts/lib/utils.js | 1 + 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/scripts/hooks/check-console-log.js b/scripts/hooks/check-console-log.js index 2cf0b09..8bbf45e 100755 --- a/scripts/hooks/check-console-log.js +++ b/scripts/hooks/check-console-log.js @@ -14,7 +14,7 @@ */ const fs = require('fs'); -const { isGitRepo, getGitModifiedFiles, log } = require('../lib/utils'); +const { isGitRepo, getGitModifiedFiles, readFile, log } = require('../lib/utils'); // Files where console.log is expected and should not trigger warnings const EXCLUDED_PATTERNS = [ @@ -49,8 +49,8 @@ process.stdin.on('end', () => { let hasConsole = false; for (const file of files) { - const content = fs.readFileSync(file, 'utf8'); - if (content.includes('console.log')) { + const content = readFile(file); + if (content && content.includes('console.log')) { log(`[Hook] WARNING: console.log found in ${file}`); hasConsole = true; } diff --git a/scripts/hooks/post-edit-console-warn.js b/scripts/hooks/post-edit-console-warn.js index a7e5d92..2607a17 100644 --- a/scripts/hooks/post-edit-console-warn.js +++ b/scripts/hooks/post-edit-console-warn.js @@ -10,6 +10,7 @@ */ const fs = require('fs'); +const { readFile } = require('../lib/utils'); const MAX_STDIN = 1024 * 1024; // 1MB limit let data = ''; @@ -25,8 +26,9 @@ process.stdin.on('end', () => { const input = JSON.parse(data); const filePath = input.tool_input?.file_path; - if (filePath && /\.(ts|tsx|js|jsx)$/.test(filePath) && fs.existsSync(filePath)) { - const content = fs.readFileSync(filePath, 'utf8'); + if (filePath && /\.(ts|tsx|js|jsx)$/.test(filePath)) { + const content = readFile(filePath); + if (!content) { console.log(data); return; } const lines = content.split('\n'); const matches = []; diff --git a/scripts/hooks/post-edit-format.js b/scripts/hooks/post-edit-format.js index adcf2b1..1957607 100644 --- a/scripts/hooks/post-edit-format.js +++ b/scripts/hooks/post-edit-format.js @@ -25,14 +25,14 @@ process.stdin.on('end', () => { const input = JSON.parse(data); const filePath = input.tool_input?.file_path; - if (filePath && /\.(ts|tsx|js|jsx)$/.test(filePath) && fs.existsSync(filePath)) { + if (filePath && /\.(ts|tsx|js|jsx)$/.test(filePath)) { try { execFileSync('npx', ['prettier', '--write', filePath], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 15000 }); } catch { - // Prettier not installed or failed — non-blocking + // Prettier not installed, file missing, or failed — non-blocking } } } catch { diff --git a/scripts/hooks/post-edit-typecheck.js b/scripts/hooks/post-edit-typecheck.js index 03746c3..83dde5b 100644 --- a/scripts/hooks/post-edit-typecheck.js +++ b/scripts/hooks/post-edit-typecheck.js @@ -27,9 +27,11 @@ process.stdin.on('end', () => { const input = JSON.parse(data); const filePath = input.tool_input?.file_path; - if (filePath && /\.(ts|tsx)$/.test(filePath) && fs.existsSync(filePath)) { + if (filePath && /\.(ts|tsx)$/.test(filePath)) { + const resolvedPath = path.resolve(filePath); + if (!fs.existsSync(resolvedPath)) { console.log(data); return; } // Find nearest tsconfig.json by walking up (max 20 levels to prevent infinite loop) - let dir = path.dirname(path.resolve(filePath)); + let dir = path.dirname(resolvedPath); const root = path.parse(dir).root; let depth = 0; diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index c724da0..a45ef73 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -366,6 +366,7 @@ function getGitModifiedFiles(patterns = []) { // Pre-compile patterns, skipping invalid ones const compiled = []; for (const pattern of patterns) { + if (typeof pattern !== 'string' || pattern.length === 0) continue; try { compiled.push(new RegExp(pattern)); } catch {