#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const ROOT_DIR = path.resolve(__dirname, '..'); const SRC_DIR = path.join(ROOT_DIR, 'src'); const REPORT_DIR = path.join(ROOT_DIR, 'docs', 'reports'); const JSON_REPORT_PATH = path.join(REPORT_DIR, 'hardening-inventory.json'); const MD_REPORT_PATH = path.join(REPORT_DIR, 'hardening-inventory.md'); const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']); const HOTPATH_PATTERNS = [ /^src\/web-server\//, /^src\/commands\//, /^src\/cliproxy\//, /^src\/management\//, /^src\/auth\//, /^src\/delegation\//, /^src\/utils\//, /^src\/ccs\.ts$/, ]; const SYNC_CALL_NAMES = [ 'accessSync', 'appendFileSync', 'chmodSync', 'chownSync', 'closeSync', 'copyFileSync', 'cpSync', 'existsSync', 'fstatSync', 'fsyncSync', 'ftruncateSync', 'futimesSync', 'lchmodSync', 'lchownSync', 'linkSync', 'lstatSync', 'mkdirSync', 'mkdtempSync', 'openSync', 'opendirSync', 'readFileSync', 'readdirSync', 'readlinkSync', 'readSync', 'readvSync', 'realpathSync', 'renameSync', 'rmSync', 'rmdirSync', 'statSync', 'symlinkSync', 'truncateSync', 'unlinkSync', 'utimesSync', 'writeFileSync', 'writeSync', 'writevSync', ]; const SYNC_CALL_CAPTURE_REGEX = new RegExp( `(?:\\bfs(?:\\s*\\?\\.)?\\s*\\.\\s*|(? pattern.test(filePath)); } function walkFiles(dirPath) { const output = []; const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { output.push(...walkFiles(fullPath)); continue; } if (entry.isFile() && isSourceFile(fullPath)) { output.push(fullPath); } } return output; } function uniqueSorted(values) { return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b)); } function sortByCountDesc(items) { return [...items].sort((a, b) => { if (b.count !== a.count) return b.count - a.count; return a.file.localeCompare(b.file); }); } function summarize(items, limit = 10) { return sortByCountDesc(items) .slice(0, limit) .map((item) => ({ file: item.file, count: item.count, calls: uniqueSorted(item.calls || []), markers: uniqueSorted(item.markers || []), })); } function isRegexLiteralStart(previousSignificantChar, previousIdentifier) { return ( previousSignificantChar === '' || '([{:;,=!?+-*%^&|~<>'.includes(previousSignificantChar) || REGEX_LITERAL_KEYWORDS.has(previousIdentifier) ); } function stripComments(sourceText) { let output = ''; let index = 0; let inLineComment = false; let inBlockComment = false; let inSingleQuote = false; let inDoubleQuote = false; let inTemplateLiteral = false; let inRegexLiteral = false; let inRegexCharClass = false; let previousSignificantChar = ''; let previousIdentifier = ''; while (index < sourceText.length) { const current = sourceText[index]; const next = sourceText[index + 1]; if (inLineComment) { if (current === '\n' || current === '\r') { inLineComment = false; output += current; } else { output += ' '; } index += 1; continue; } if (inBlockComment) { if (current === '*' && next === '/') { output += ' '; index += 2; inBlockComment = false; continue; } output += current === '\n' || current === '\r' ? current : ' '; index += 1; continue; } if (inRegexLiteral) { if (current === '\n' || current === '\r') { output += current; index += 1; inRegexLiteral = false; inRegexCharClass = false; continue; } output += ' '; if (current === '\\') { output += next === '\n' || next === '\r' ? next : ' '; index += 2; continue; } if (!inRegexCharClass && current === '[') { inRegexCharClass = true; index += 1; continue; } if (inRegexCharClass && current === ']') { inRegexCharClass = false; index += 1; continue; } if (!inRegexCharClass && current === '/') { index += 1; while (index < sourceText.length && /[a-z]/i.test(sourceText[index])) { output += ' '; index += 1; } inRegexLiteral = false; previousSignificantChar = 'r'; previousIdentifier = ''; continue; } index += 1; continue; } if (inSingleQuote) { output += current === '\n' || current === '\r' ? current : ' '; if (current === '\\') { output += next === '\n' || next === '\r' ? next : ' '; index += 2; continue; } if (current === "'") { inSingleQuote = false; previousSignificantChar = 's'; previousIdentifier = ''; } index += 1; continue; } if (inDoubleQuote) { output += current === '\n' || current === '\r' ? current : ' '; if (current === '\\') { output += next === '\n' || next === '\r' ? next : ' '; index += 2; continue; } if (current === '"') { inDoubleQuote = false; previousSignificantChar = 's'; previousIdentifier = ''; } index += 1; continue; } if (inTemplateLiteral) { output += current === '\n' || current === '\r' ? current : ' '; if (current === '\\') { output += next === '\n' || next === '\r' ? next : ' '; index += 2; continue; } if (current === '`') { inTemplateLiteral = false; previousSignificantChar = 's'; previousIdentifier = ''; } index += 1; continue; } if (current === '/' && next === '/') { output += ' '; index += 2; inLineComment = true; continue; } if (current === '/' && next === '*') { output += ' '; index += 2; inBlockComment = true; continue; } if (current === '/' && isRegexLiteralStart(previousSignificantChar, previousIdentifier)) { output += ' '; index += 1; inRegexLiteral = true; inRegexCharClass = false; continue; } if (/[A-Za-z_$]/.test(current)) { let tokenEnd = index + 1; while (tokenEnd < sourceText.length && /[A-Za-z0-9_$]/.test(sourceText[tokenEnd])) { tokenEnd += 1; } const token = sourceText.slice(index, tokenEnd); output += token; previousSignificantChar = 'i'; previousIdentifier = token; index = tokenEnd; continue; } if (current === "'") { inSingleQuote = true; output += ' '; index += 1; continue; } if (current === '"') { inDoubleQuote = true; output += ' '; index += 1; continue; } if (current === '`') { inTemplateLiteral = true; output += ' '; index += 1; continue; } output += current; if (!/\s/.test(current)) { previousSignificantChar = current; previousIdentifier = ''; } index += 1; } return output; } function collectSyncCallSites(sourceText) { const sanitizedSource = stripComments(sourceText); const captureRegex = new RegExp(SYNC_CALL_CAPTURE_REGEX.source, SYNC_CALL_CAPTURE_REGEX.flags); const calls = []; for (const match of sanitizedSource.matchAll(captureRegex)) { calls.push(match[1]); } return { count: calls.length, calls, }; } function buildReport() { const files = walkFiles(SRC_DIR); const syncEntries = []; const legacyEntries = []; for (const fullPath of files) { const file = relativePath(fullPath); const sourceText = fs.readFileSync(fullPath, 'utf8'); const lines = sourceText.split(/\r?\n/); const { count: syncCount, calls: syncCalls } = collectSyncCallSites(sourceText); let legacyCount = 0; const legacyMarkers = []; for (const line of lines) { if (LEGACY_MARKER_REGEX.test(line)) { legacyCount += 1; const normalized = line.trim(); if (normalized.length > 0) { legacyMarkers.push(normalized); } } } if (syncCount > 0) { syncEntries.push({ file, count: syncCount, calls: syncCalls, hotpath: isHotpath(file), }); } if (legacyCount > 0) { legacyEntries.push({ file, count: legacyCount, markers: legacyMarkers, }); } } const syncHotpathEntries = syncEntries.filter((entry) => entry.hotpath); const totalSyncCount = syncEntries.reduce((acc, entry) => acc + entry.count, 0); const totalSyncHotpathCount = syncHotpathEntries.reduce((acc, entry) => acc + entry.count, 0); const totalLegacyMarkers = legacyEntries.reduce((acc, entry) => acc + entry.count, 0); return { scope: 'src/**/*.{ts,tsx,js,jsx,mjs,cjs}', syncFs: { totalOccurrences: totalSyncCount, filesAffected: syncEntries.length, hotpathOccurrences: totalSyncHotpathCount, hotpathFilesAffected: syncHotpathEntries.length, topHotpathFiles: summarize(syncHotpathEntries), topFilesOverall: summarize(syncEntries), }, legacyShim: { totalMarkers: totalLegacyMarkers, filesAffected: legacyEntries.length, topFiles: summarize(legacyEntries), explicitShimFiles: uniqueSorted( legacyEntries .map((entry) => entry.file) .filter((file) => /shim|re-export|compat/i.test(path.basename(file))) ), }, }; } function renderMarkdown(report) { const lines = []; lines.push('# Hardening Inventory Report'); lines.push(''); lines.push(`Scope: \`${report.scope}\``); lines.push(''); lines.push('## Summary'); lines.push(''); lines.push('| Metric | Value |'); lines.push('|---|---:|'); lines.push(`| Sync fs occurrences (all) | ${report.syncFs.totalOccurrences} |`); lines.push(`| Sync fs files affected (all) | ${report.syncFs.filesAffected} |`); lines.push(`| Sync fs occurrences (runtime hotpaths) | ${report.syncFs.hotpathOccurrences} |`); lines.push(`| Sync fs files affected (runtime hotpaths) | ${report.syncFs.hotpathFilesAffected} |`); lines.push(`| Legacy shim markers | ${report.legacyShim.totalMarkers} |`); lines.push(`| Legacy shim files affected | ${report.legacyShim.filesAffected} |`); lines.push(''); lines.push('## Top Runtime Hotpath Sync fs Files'); lines.push(''); lines.push('| File | Sync Calls | API Names |'); lines.push('|---|---:|---|'); for (const item of report.syncFs.topHotpathFiles) { lines.push(`| \`${item.file}\` | ${item.count} | ${item.calls.join(', ')} |`); } if (report.syncFs.topHotpathFiles.length === 0) { lines.push('| _none_ | 0 | - |'); } lines.push(''); lines.push('## Top Legacy Shim Marker Files'); lines.push(''); lines.push('| File | Marker Count |'); lines.push('|---|---:|'); for (const item of report.legacyShim.topFiles) { lines.push(`| \`${item.file}\` | ${item.count} |`); } if (report.legacyShim.topFiles.length === 0) { lines.push('| _none_ | 0 |'); } lines.push(''); lines.push('## Explicit Shim/Re-export Files'); lines.push(''); for (const file of report.legacyShim.explicitShimFiles) { lines.push(`- \`${file}\``); } if (report.legacyShim.explicitShimFiles.length === 0) { lines.push('- _none_'); } lines.push(''); return lines.join('\n'); } function main() { const report = buildReport(); fs.mkdirSync(REPORT_DIR, { recursive: true }); fs.writeFileSync(JSON_REPORT_PATH, JSON.stringify(report, null, 2) + '\n', 'utf8'); fs.writeFileSync(MD_REPORT_PATH, renderMarkdown(report), 'utf8'); const relJson = relativePath(JSON_REPORT_PATH); const relMd = relativePath(MD_REPORT_PATH); console.log(`[hardening-inventory] generatedAt=${new Date().toISOString()}`); console.log( `[hardening-inventory] sync-fs total=${report.syncFs.totalOccurrences}, hotpath=${report.syncFs.hotpathOccurrences}` ); console.log( `[hardening-inventory] legacy markers total=${report.legacyShim.totalMarkers}, files=${report.legacyShim.filesAffected}` ); console.log(`[hardening-inventory] wrote ${relJson}`); console.log(`[hardening-inventory] wrote ${relMd}`); } if (require.main === module) { main(); } module.exports = { buildReport, collectSyncCallSites, renderMarkdown, stripComments, };