fix(security): patch 5 vulnerabilities from security audit

- mask API keys in usage stats/history responses (apiKeyMasked)
- validate proxy URL scheme + reject shell metachars before env write
- escape HTML in OAuth callback page to prevent XSS
- atomic O_EXCL lock file to prevent TOCTOU race in MITM startServer
- set mitmIsRestarting guard synchronously before any await

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
hamsa0x7
2026-06-26 11:01:13 +07:00
committed by decolua
parent 520f5049bf
commit d8c2298d07
5 changed files with 364 additions and 14 deletions

View File

@@ -41,6 +41,7 @@ async function resolveMitmRouterBaseUrl() {
const MITM_PORT = 443;
const MITM_WIN_NODE_PORT = 8443;
const PID_FILE = path.join(MITM_DIR, ".mitm.pid");
const LOCK_FILE = path.join(MITM_DIR, ".mitm.lock");
const MITM_MAX_RESTARTS = 5;
const MITM_RESTART_DELAYS_MS = [5000, 10000, 20000, 30000, 60000];
@@ -400,19 +401,22 @@ async function getMitmStatus() {
async function scheduleMitmRestart(apiKey) {
if (mitmIsRestarting) return;
// Set guard synchronously before any await to prevent concurrent calls
// from passing the check above.
mitmIsRestarting = true;
const aliveMs = Date.now() - mitmLastStartTime;
if (aliveMs >= MITM_RESTART_RESET_MS) mitmRestartCount = 0;
if (mitmRestartCount >= MITM_MAX_RESTARTS) {
err("Max restart attempts reached. Giving up.");
mitmIsRestarting = false;
return;
}
const attempt = mitmRestartCount;
const delay = MITM_RESTART_DELAYS_MS[Math.min(attempt, MITM_RESTART_DELAYS_MS.length - 1)];
mitmRestartCount++;
mitmIsRestarting = true;
log(`Restarting in ${delay / 1000}s... (${mitmRestartCount}/${MITM_MAX_RESTARTS})`);
await new Promise((r) => setTimeout(r, delay));
@@ -486,7 +490,19 @@ async function startServer(apiKey, sudoPassword, forceKillPort443 = false) {
throw new Error("MITM server is already running");
}
await killLeftoverMitm(sudoPassword);
// Atomically claim lock to prevent concurrent startServer across processes.
// O_EXCL (flag: "wx") fails with EEXIST if the file already exists.
try {
fs.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
} catch (e) {
if (e.code === "EEXIST") {
throw new Error("MITM server is already starting (lock contention)");
}
throw e;
}
try {
await killLeftoverMitm(sudoPassword);
if (!IS_WIN) {
const portStatus = await checkPort443Free();
@@ -679,6 +695,7 @@ async function startServer(apiKey, sudoPassword, forceKillPort443 = false) {
serverProcess = null;
serverPid = null;
try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
try { fs.unlinkSync(LOCK_FILE); } catch { /* ignore */ }
// Auto-restart on unexpected exit
if (code !== 0 && !mitmIsRestarting) scheduleMitmRestart(apiKey);
});
@@ -706,7 +723,15 @@ async function startServer(apiKey, sudoPassword, forceKillPort443 = false) {
await saveMitmSettings(true, sudoPassword);
if (sudoPassword) setCachedPassword(sudoPassword);
// Server is healthy — remove lock file (PID file persists as the marker)
try { fs.unlinkSync(LOCK_FILE); } catch { /* ignore */ }
return { running: true, pid: serverPid };
} catch (e) {
// Clean up lock on any failure
try { fs.unlinkSync(LOCK_FILE); } catch { /* ignore */ }
throw e;
}
}
/**
@@ -779,6 +804,7 @@ async function stopServer(sudoPassword) {
}
try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
try { fs.unlinkSync(LOCK_FILE); } catch { /* ignore */ }
await saveMitmSettings(false, null);
mitmIsRestarting = false;