refactor: replace fs with secureFs for improved file handling

This commit updates various modules to utilize the secure file system operations from the secureFs module instead of the native fs module. Key changes include:

- Replaced fs imports with secureFs in multiple route handlers and services to enhance security and consistency in file operations.
- Added centralized validation for working directories in the sdk-options module to ensure all AI model invocations are secure.

These changes aim to improve the security and maintainability of file handling across the application.
This commit is contained in:
Test User
2025-12-21 01:32:26 -05:00
parent 2b5479ae0d
commit 077a63b03b
62 changed files with 4866 additions and 3350 deletions

View File

@@ -7,10 +7,10 @@
* Developers should configure their projects to use the PORT environment variable.
*/
import { spawn, execSync, type ChildProcess } from "child_process";
import { existsSync } from "fs";
import path from "path";
import net from "net";
import { spawn, execSync, type ChildProcess } from 'child_process';
import * as secureFs from '../lib/secure-fs.js';
import path from 'path';
import net from 'net';
export interface DevServerInfo {
worktreePath: string;
@@ -40,12 +40,12 @@ class DevServerService {
// Then check if the system has it in use
return new Promise((resolve) => {
const server = net.createServer();
server.once("error", () => resolve(false));
server.once("listening", () => {
server.once('error', () => resolve(false));
server.once('listening', () => {
server.close();
resolve(true);
});
server.listen(port, "127.0.0.1");
server.listen(port, '127.0.0.1');
});
}
@@ -54,21 +54,21 @@ class DevServerService {
*/
private killProcessOnPort(port: number): void {
try {
if (process.platform === "win32") {
if (process.platform === 'win32') {
// Windows: find and kill process on port
const result = execSync(`netstat -ano | findstr :${port}`, { encoding: "utf-8" });
const lines = result.trim().split("\n");
const result = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf-8' });
const lines = result.trim().split('\n');
const pids = new Set<string>();
for (const line of lines) {
const parts = line.trim().split(/\s+/);
const pid = parts[parts.length - 1];
if (pid && pid !== "0") {
if (pid && pid !== '0') {
pids.add(pid);
}
}
for (const pid of pids) {
try {
execSync(`taskkill /F /PID ${pid}`, { stdio: "ignore" });
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
console.log(`[DevServerService] Killed process ${pid} on port ${port}`);
} catch {
// Process may have already exited
@@ -77,11 +77,11 @@ class DevServerService {
} else {
// macOS/Linux: use lsof to find and kill process
try {
const result = execSync(`lsof -ti:${port}`, { encoding: "utf-8" });
const pids = result.trim().split("\n").filter(Boolean);
const result = execSync(`lsof -ti:${port}`, { encoding: 'utf-8' });
const pids = result.trim().split('\n').filter(Boolean);
for (const pid of pids) {
try {
execSync(`kill -9 ${pid}`, { stdio: "ignore" });
execSync(`kill -9 ${pid}`, { stdio: 'ignore' });
console.log(`[DevServerService] Killed process ${pid} on port ${port}`);
} catch {
// Process may have already exited
@@ -127,37 +127,47 @@ class DevServerService {
throw new Error(`No available ports found between ${BASE_PORT} and ${MAX_PORT}`);
}
/**
* Helper to check if a file exists using secureFs
*/
private async fileExists(filePath: string): Promise<boolean> {
try {
await secureFs.access(filePath);
return true;
} catch {
return false;
}
}
/**
* Detect the package manager used in a directory
*/
private detectPackageManager(
dir: string
): "npm" | "yarn" | "pnpm" | "bun" | null {
if (existsSync(path.join(dir, "bun.lockb"))) return "bun";
if (existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
if (existsSync(path.join(dir, "yarn.lock"))) return "yarn";
if (existsSync(path.join(dir, "package-lock.json"))) return "npm";
if (existsSync(path.join(dir, "package.json"))) return "npm"; // Default
private async detectPackageManager(dir: string): Promise<'npm' | 'yarn' | 'pnpm' | 'bun' | null> {
if (await this.fileExists(path.join(dir, 'bun.lockb'))) return 'bun';
if (await this.fileExists(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
if (await this.fileExists(path.join(dir, 'yarn.lock'))) return 'yarn';
if (await this.fileExists(path.join(dir, 'package-lock.json'))) return 'npm';
if (await this.fileExists(path.join(dir, 'package.json'))) return 'npm'; // Default
return null;
}
/**
* Get the dev script command for a directory
*/
private getDevCommand(dir: string): { cmd: string; args: string[] } | null {
const pm = this.detectPackageManager(dir);
private async getDevCommand(dir: string): Promise<{ cmd: string; args: string[] } | null> {
const pm = await this.detectPackageManager(dir);
if (!pm) return null;
switch (pm) {
case "bun":
return { cmd: "bun", args: ["run", "dev"] };
case "pnpm":
return { cmd: "pnpm", args: ["run", "dev"] };
case "yarn":
return { cmd: "yarn", args: ["dev"] };
case "npm":
case 'bun':
return { cmd: 'bun', args: ['run', 'dev'] };
case 'pnpm':
return { cmd: 'pnpm', args: ['run', 'dev'] };
case 'yarn':
return { cmd: 'yarn', args: ['dev'] };
case 'npm':
default:
return { cmd: "npm", args: ["run", "dev"] };
return { cmd: 'npm', args: ['run', 'dev'] };
}
}
@@ -192,7 +202,7 @@ class DevServerService {
}
// Verify the worktree exists
if (!existsSync(worktreePath)) {
if (!(await this.fileExists(worktreePath))) {
return {
success: false,
error: `Worktree path does not exist: ${worktreePath}`,
@@ -200,8 +210,8 @@ class DevServerService {
}
// Check for package.json
const packageJsonPath = path.join(worktreePath, "package.json");
if (!existsSync(packageJsonPath)) {
const packageJsonPath = path.join(worktreePath, 'package.json');
if (!(await this.fileExists(packageJsonPath))) {
return {
success: false,
error: `No package.json found in: ${worktreePath}`,
@@ -209,7 +219,7 @@ class DevServerService {
}
// Get dev command
const devCommand = this.getDevCommand(worktreePath);
const devCommand = await this.getDevCommand(worktreePath);
if (!devCommand) {
return {
success: false,
@@ -224,7 +234,7 @@ class DevServerService {
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Port allocation failed",
error: error instanceof Error ? error.message : 'Port allocation failed',
};
}
@@ -241,14 +251,10 @@ class DevServerService {
// Small delay to ensure related ports are freed
await new Promise((resolve) => setTimeout(resolve, 100));
console.log(`[DevServerService] Starting dev server on port ${port}`);
console.log(`[DevServerService] Working directory (cwd): ${worktreePath}`);
console.log(
`[DevServerService] Starting dev server on port ${port}`
);
console.log(
`[DevServerService] Working directory (cwd): ${worktreePath}`
);
console.log(
`[DevServerService] Command: ${devCommand.cmd} ${devCommand.args.join(" ")} with PORT=${port}`
`[DevServerService] Command: ${devCommand.cmd} ${devCommand.args.join(' ')} with PORT=${port}`
);
// Spawn the dev process with PORT environment variable
@@ -260,7 +266,7 @@ class DevServerService {
const devProcess = spawn(devCommand.cmd, devCommand.args, {
cwd: worktreePath,
env,
stdio: ["ignore", "pipe", "pipe"],
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
});
@@ -269,29 +275,27 @@ class DevServerService {
// Log output for debugging
if (devProcess.stdout) {
devProcess.stdout.on("data", (data: Buffer) => {
devProcess.stdout.on('data', (data: Buffer) => {
console.log(`[DevServer:${port}] ${data.toString().trim()}`);
});
}
if (devProcess.stderr) {
devProcess.stderr.on("data", (data: Buffer) => {
devProcess.stderr.on('data', (data: Buffer) => {
const msg = data.toString().trim();
console.error(`[DevServer:${port}] ${msg}`);
});
}
devProcess.on("error", (error) => {
devProcess.on('error', (error) => {
console.error(`[DevServerService] Process error:`, error);
status.error = error.message;
this.allocatedPorts.delete(port);
this.runningServers.delete(worktreePath);
});
devProcess.on("exit", (code) => {
console.log(
`[DevServerService] Process for ${worktreePath} exited with code ${code}`
);
devProcess.on('exit', (code) => {
console.log(`[DevServerService] Process for ${worktreePath} exited with code ${code}`);
status.exited = true;
this.allocatedPorts.delete(port);
this.runningServers.delete(worktreePath);
@@ -348,7 +352,9 @@ class DevServerService {
// If we don't have a record of this server, it may have crashed/exited on its own
// Return success so the frontend can clear its state
if (!server) {
console.log(`[DevServerService] No server record for ${worktreePath}, may have already stopped`);
console.log(
`[DevServerService] No server record for ${worktreePath}, may have already stopped`
);
return {
success: true,
result: {
@@ -362,7 +368,7 @@ class DevServerService {
// Kill the process
if (server.process && !server.process.killed) {
server.process.kill("SIGTERM");
server.process.kill('SIGTERM');
}
// Free the port
@@ -447,13 +453,13 @@ export function getDevServerService(): DevServerService {
}
// Cleanup on process exit
process.on("SIGTERM", async () => {
process.on('SIGTERM', async () => {
if (devServerServiceInstance) {
await devServerServiceInstance.stopAll();
}
});
process.on("SIGINT", async () => {
process.on('SIGINT', async () => {
if (devServerServiceInstance) {
await devServerServiceInstance.stopAll();
}