fix(hardening): count executable sync fs call sites

This commit is contained in:
Tam Nhu Tran
2026-02-12 14:40:45 +07:00
parent cefb564948
commit d21b5c44ee
4 changed files with 186 additions and 58 deletions
+2 -2
View File
@@ -31,9 +31,9 @@ Baseline captured: `2026-02-12`.
| Metric | Baseline |
|---|---:|
| Sync fs occurrences (all) | 841 |
| Sync fs occurrences (all) | 835 |
| Sync fs files affected (all) | 100 |
| Sync fs occurrences (runtime hotpaths) | 730 |
| Sync fs occurrences (runtime hotpaths) | 724 |
| Sync fs files affected (runtime hotpaths) | 89 |
| Legacy shim markers | 131 |
| Legacy shim files affected | 56 |
+38 -38
View File
@@ -1,9 +1,9 @@
{
"scope": "src/**/*.{ts,tsx,js,jsx,mjs,cjs}",
"syncFs": {
"totalOccurrences": 841,
"totalOccurrences": 835,
"filesAffected": 100,
"hotpathOccurrences": 730,
"hotpathOccurrences": 724,
"hotpathFilesAffected": 89,
"topHotpathFiles": [
{
@@ -44,24 +44,6 @@
],
"markers": []
},
{
"file": "src/utils/claude-dir-installer.ts",
"count": 23,
"calls": [
"copyFileSync",
"cpSync",
"existsSync",
"lstatSync",
"mkdirSync",
"readdirSync",
"renameSync",
"rmSync",
"statSync",
"unlinkSync",
"writeFileSync"
],
"markers": []
},
{
"file": "src/utils/shell-completion.ts",
"count": 23,
@@ -89,6 +71,24 @@
],
"markers": []
},
{
"file": "src/utils/claude-dir-installer.ts",
"count": 21,
"calls": [
"copyFileSync",
"cpSync",
"existsSync",
"lstatSync",
"mkdirSync",
"readdirSync",
"renameSync",
"rmSync",
"statSync",
"unlinkSync",
"writeFileSync"
],
"markers": []
},
{
"file": "src/cliproxy/binary/version-cache.ts",
"count": 20,
@@ -218,24 +218,6 @@
],
"markers": []
},
{
"file": "src/utils/claude-dir-installer.ts",
"count": 23,
"calls": [
"copyFileSync",
"cpSync",
"existsSync",
"lstatSync",
"mkdirSync",
"readdirSync",
"renameSync",
"rmSync",
"statSync",
"unlinkSync",
"writeFileSync"
],
"markers": []
},
{
"file": "src/utils/shell-completion.ts",
"count": 23,
@@ -263,6 +245,24 @@
],
"markers": []
},
{
"file": "src/utils/claude-dir-installer.ts",
"count": 21,
"calls": [
"copyFileSync",
"cpSync",
"existsSync",
"lstatSync",
"mkdirSync",
"readdirSync",
"renameSync",
"rmSync",
"statSync",
"unlinkSync",
"writeFileSync"
],
"markers": []
},
{
"file": "src/cliproxy/binary/version-cache.ts",
"count": 20,
+3 -3
View File
@@ -6,9 +6,9 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
| Metric | Value |
|---|---:|
| Sync fs occurrences (all) | 841 |
| Sync fs occurrences (all) | 835 |
| Sync fs files affected (all) | 100 |
| Sync fs occurrences (runtime hotpaths) | 730 |
| Sync fs occurrences (runtime hotpaths) | 724 |
| Sync fs files affected (runtime hotpaths) | 89 |
| Legacy shim markers | 131 |
| Legacy shim files affected | 56 |
@@ -19,9 +19,9 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
|---|---:|---|
| `src/management/shared-manager.ts` | 60 | copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync |
| `src/utils/claude-symlink-manager.ts` | 27 | copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync |
| `src/utils/claude-dir-installer.ts` | 23 | copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync |
| `src/utils/shell-completion.ts` | 23 | appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync |
| `src/web-server/routes/settings-routes.ts` | 23 | copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync |
| `src/utils/claude-dir-installer.ts` | 21 | copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync |
| `src/cliproxy/binary/version-cache.ts` | 20 | existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync |
| `src/management/recovery-manager.ts` | 20 | copyFileSync, existsSync, mkdirSync, renameSync, writeFileSync |
| `src/web-server/routes/cliproxy-stats-routes.ts` | 20 | closeSync, existsSync, fstatSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, renameSync, statSync, writeFileSync |
+143 -15
View File
@@ -60,9 +60,10 @@ const SYNC_CALL_NAMES = [
'writeSync',
'writevSync',
];
const SYNC_CALL_LINE_REGEX = new RegExp(`\\b(?:fs\\.)?(?:${SYNC_CALL_NAMES.join('|')})\\b`);
const SYNC_CALL_CAPTURE_REGEX = new RegExp(`\\b(?:fs\\.)?(${SYNC_CALL_NAMES.join('|')})\\b`, 'g');
const SYNC_CALL_CAPTURE_REGEX = new RegExp(
`(?:\\bfs(?:\\s*\\?\\.)?\\s*\\.\\s*|(?<![\\w$.]))(${SYNC_CALL_NAMES.join('|')})\\s*\\(`,
'g'
);
const LEGACY_MARKER_REGEX =
/(?:\blegacy\b|\bshim\b|backward compatibility|backwards compatibility|compatibility layer|deprecated.*re-export|re-export.*compatibility)/i;
@@ -123,6 +124,142 @@ function summarize(items, limit = 10) {
}));
}
function stripComments(sourceText) {
let output = '';
let index = 0;
let inLineComment = false;
let inBlockComment = false;
let inSingleQuote = false;
let inDoubleQuote = false;
let inTemplateLiteral = false;
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 (inSingleQuote) {
output += current;
if (current === '\\') {
output += next ?? '';
index += 2;
continue;
}
if (current === "'") {
inSingleQuote = false;
}
index += 1;
continue;
}
if (inDoubleQuote) {
output += current;
if (current === '\\') {
output += next ?? '';
index += 2;
continue;
}
if (current === '"') {
inDoubleQuote = false;
}
index += 1;
continue;
}
if (inTemplateLiteral) {
output += current;
if (current === '\\') {
output += next ?? '';
index += 2;
continue;
}
if (current === '`') {
inTemplateLiteral = false;
}
index += 1;
continue;
}
if (current === '/' && next === '/') {
output += ' ';
index += 2;
inLineComment = true;
continue;
}
if (current === '/' && next === '*') {
output += ' ';
index += 2;
inBlockComment = true;
continue;
}
if (current === "'") {
inSingleQuote = true;
output += current;
index += 1;
continue;
}
if (current === '"') {
inDoubleQuote = true;
output += current;
index += 1;
continue;
}
if (current === '`') {
inTemplateLiteral = true;
output += current;
index += 1;
continue;
}
output += current;
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 = [];
@@ -130,22 +267,13 @@ function buildReport() {
for (const fullPath of files) {
const file = relativePath(fullPath);
const lines = fs.readFileSync(fullPath, 'utf8').split(/\r?\n/);
let syncCount = 0;
const syncCalls = [];
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 (SYNC_CALL_LINE_REGEX.test(line)) {
const matches = [...line.matchAll(SYNC_CALL_CAPTURE_REGEX)];
syncCount += matches.length;
for (const match of matches) {
syncCalls.push(match[1]);
}
}
if (LEGACY_MARKER_REGEX.test(line)) {
legacyCount += 1;
const normalized = line.trim();