diff --git a/scripts/lib/session-aliases.js b/scripts/lib/session-aliases.js index 4d14de3..1260f1c 100644 --- a/scripts/lib/session-aliases.js +++ b/scripts/lib/session-aliases.js @@ -194,6 +194,10 @@ function setAlias(alias, sessionPath, title = null) { return { success: false, error: 'Session path cannot be empty' }; } + if (alias.length > 128) { + return { success: false, error: 'Alias name cannot exceed 128 characters' }; + } + if (!/^[a-zA-Z0-9_-]+$/.test(alias)) { return { success: false, error: 'Alias name must contain only letters, numbers, dashes, and underscores' }; } diff --git a/scripts/lib/utils.d.ts b/scripts/lib/utils.d.ts index 7c5a0b1..3e04474 100644 --- a/scripts/lib/utils.d.ts +++ b/scripts/lib/utils.d.ts @@ -123,6 +123,11 @@ export interface ReadStdinJsonOptions { * if stdin never closes. Default: 5000 */ timeoutMs?: number; + /** + * Maximum stdin data size in bytes. Prevents unbounded memory growth. + * Default: 1048576 (1MB) + */ + maxSize?: number; } /** diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index 39954f6..c724da0 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -62,7 +62,7 @@ function ensureDir(dirPath) { } catch (err) { // EEXIST is fine (race condition with another process creating it) if (err.code !== 'EEXIST') { - throw err; + throw new Error(`Failed to create directory '${dirPath}': ${err.message}`); } } return dirPath; @@ -140,6 +140,9 @@ function getDateTimeString() { * @param {object} options - Options { maxAge: days, recursive: boolean } */ function findFiles(dir, pattern, options = {}) { + if (!dir || typeof dir !== 'string') return []; + if (!pattern || typeof pattern !== 'string') return []; + const { maxAge = null, recursive = false } = options; const results = []; @@ -201,7 +204,7 @@ function findFiles(dir, pattern, options = {}) { * @returns {Promise} Parsed JSON object, or empty object if stdin is empty */ async function readStdinJson(options = {}) { - const { timeoutMs = 5000 } = options; + const { timeoutMs = 5000, maxSize = 1024 * 1024 } = options; return new Promise((resolve, reject) => { let data = ''; @@ -221,7 +224,9 @@ async function readStdinJson(options = {}) { process.stdin.setEncoding('utf8'); process.stdin.on('data', chunk => { - data += chunk; + if (data.length < maxSize) { + data += chunk; + } }); process.stdin.on('end', () => { @@ -382,9 +387,14 @@ function replaceInFile(filePath, search, replace) { const content = readFile(filePath); if (content === null) return false; - const newContent = content.replace(search, replace); - writeFile(filePath, newContent); - return true; + try { + const newContent = content.replace(search, replace); + writeFile(filePath, newContent); + return true; + } catch (err) { + log(`[Utils] replaceInFile failed for ${filePath}: ${err.message}`); + return false; + } } /** @@ -400,11 +410,17 @@ function countInFile(filePath, pattern) { if (content === null) return 0; let regex; - if (pattern instanceof RegExp) { - // Ensure global flag is set for correct counting - regex = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g'); - } else { - regex = new RegExp(pattern, 'g'); + try { + if (pattern instanceof RegExp) { + // Ensure global flag is set for correct counting + regex = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g'); + } else if (typeof pattern === 'string') { + regex = new RegExp(pattern, 'g'); + } else { + return 0; + } + } catch { + return 0; // Invalid regex pattern } const matches = content.match(regex); return matches ? matches.length : 0; @@ -417,7 +433,12 @@ function grepFile(filePath, pattern) { const content = readFile(filePath); if (content === null) return []; - const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern); + let regex; + try { + regex = pattern instanceof RegExp ? pattern : new RegExp(pattern); + } catch { + return []; // Invalid regex pattern + } const lines = content.split('\n'); const results = [];