refactor: integrate secure file system operations across services

This commit replaces direct file system operations with a secure file system adapter to enhance security by enforcing path validation. The changes include:

- Replaced `fs` imports with `secureFs` in various services and utilities.
- Updated file operations in `agent-service`, `auto-mode-service`, `feature-loader`, and `settings-service` to use the secure file system methods.
- Ensured that all file I/O operations are validated against the ALLOWED_ROOT_DIRECTORY.

This refactor aims to prevent unauthorized file access and improve overall security posture.

Tests: All unit tests passing.

🤖 Generated with Claude Code
This commit is contained in:
Test User
2025-12-20 18:45:39 -05:00
parent ade80484bb
commit f3c9e828e2
45 changed files with 329 additions and 551 deletions

View File

@@ -5,6 +5,7 @@
import { Router } from "express";
import { AgentService } from "../../services/agent-service.js";
import type { EventEmitter } from "../../lib/events.js";
import { validatePathParams } from "../../middleware/validate-paths.js";
import { createStartHandler } from "./routes/start.js";
import { createSendHandler } from "./routes/send.js";
import { createHistoryHandler } from "./routes/history.js";
@@ -18,8 +19,8 @@ export function createAgentRoutes(
): Router {
const router = Router();
router.post("/start", createStartHandler(agentService));
router.post("/send", createSendHandler(agentService));
router.post("/start", validatePathParams("workingDirectory?"), createStartHandler(agentService));
router.post("/send", validatePathParams("workingDirectory?", "imagePaths[]"), createSendHandler(agentService));
router.post("/history", createHistoryHandler(agentService));
router.post("/stop", createStopHandler(agentService));
router.post("/clear", createClearHandler(agentService));

View File

@@ -6,8 +6,6 @@ import type { Request, Response } from "express";
import { AgentService } from "../../../services/agent-service.js";
import { createLogger } from "../../../lib/logger.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const logger = createLogger("Agent");
export function createSendHandler(agentService: AgentService) {
@@ -30,27 +28,6 @@ export function createSendHandler(agentService: AgentService) {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
if (workingDirectory) {
validatePath(workingDirectory);
}
if (imagePaths && imagePaths.length > 0) {
for (const imagePath of imagePaths) {
validatePath(imagePath);
}
}
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Start the message processing (don't await - it streams via WebSocket)
agentService
.sendMessage({

View File

@@ -6,8 +6,6 @@ import type { Request, Response } from "express";
import { AgentService } from "../../../services/agent-service.js";
import { createLogger } from "../../../lib/logger.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const logger = createLogger("Agent");
export function createStartHandler(agentService: AgentService) {
@@ -25,22 +23,6 @@ export function createStartHandler(agentService: AgentService) {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
if (workingDirectory) {
try {
validatePath(workingDirectory);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
}
const result = await agentService.startConversation({
sessionId,
workingDirectory,

View File

@@ -6,6 +6,7 @@
import { Router } from "express";
import type { AutoModeService } from "../../services/auto-mode-service.js";
import { validatePathParams } from "../../middleware/validate-paths.js";
import { createStopFeatureHandler } from "./routes/stop-feature.js";
import { createStatusHandler } from "./routes/status.js";
import { createRunFeatureHandler } from "./routes/run-feature.js";
@@ -22,16 +23,16 @@ export function createAutoModeRoutes(autoModeService: AutoModeService): Router {
router.post("/stop-feature", createStopFeatureHandler(autoModeService));
router.post("/status", createStatusHandler(autoModeService));
router.post("/run-feature", createRunFeatureHandler(autoModeService));
router.post("/run-feature", validatePathParams("projectPath"), createRunFeatureHandler(autoModeService));
router.post("/verify-feature", createVerifyFeatureHandler(autoModeService));
router.post("/resume-feature", createResumeFeatureHandler(autoModeService));
router.post("/context-exists", createContextExistsHandler(autoModeService));
router.post("/analyze-project", createAnalyzeProjectHandler(autoModeService));
router.post("/analyze-project", validatePathParams("projectPath"), createAnalyzeProjectHandler(autoModeService));
router.post(
"/follow-up-feature",
createFollowUpFeatureHandler(autoModeService)
);
router.post("/commit-feature", createCommitFeatureHandler(autoModeService));
router.post("/commit-feature", validatePathParams("projectPath", "worktreePath?"), createCommitFeatureHandler(autoModeService));
router.post("/approve-plan", createApprovePlanHandler(autoModeService));
return router;

View File

@@ -6,7 +6,6 @@ import type { Request, Response } from "express";
import type { AutoModeService } from "../../../services/auto-mode-service.js";
import { createLogger } from "../../../lib/logger.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const logger = createLogger("AutoMode");
@@ -22,20 +21,6 @@ export function createAnalyzeProjectHandler(autoModeService: AutoModeService) {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Start analysis in background
autoModeService.analyzeProject(projectPath).catch((error) => {
logger.error(`[AutoMode] Project analysis error:`, error);

View File

@@ -5,7 +5,6 @@
import type { Request, Response } from "express";
import type { AutoModeService } from "../../../services/auto-mode-service.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
export function createCommitFeatureHandler(autoModeService: AutoModeService) {
return async (req: Request, res: Response): Promise<void> => {
@@ -26,23 +25,6 @@ export function createCommitFeatureHandler(autoModeService: AutoModeService) {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
if (worktreePath) {
validatePath(worktreePath);
}
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const commitHash = await autoModeService.commitFeature(
projectPath,
featureId,

View File

@@ -6,7 +6,6 @@ import type { Request, Response } from "express";
import type { AutoModeService } from "../../../services/auto-mode-service.js";
import { createLogger } from "../../../lib/logger.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const logger = createLogger("AutoMode");
@@ -27,20 +26,6 @@ export function createRunFeatureHandler(autoModeService: AutoModeService) {
return;
}
// Validate path is within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Start execution in background
// executeFeature derives workDir from feature.branchName
autoModeService

View File

@@ -4,6 +4,7 @@
import { Router } from "express";
import { FeatureLoader } from "../../services/feature-loader.js";
import { validatePathParams } from "../../middleware/validate-paths.js";
import { createListHandler } from "./routes/list.js";
import { createGetHandler } from "./routes/get.js";
import { createCreateHandler } from "./routes/create.js";
@@ -15,11 +16,11 @@ import { createGenerateTitleHandler } from "./routes/generate-title.js";
export function createFeaturesRoutes(featureLoader: FeatureLoader): Router {
const router = Router();
router.post("/list", createListHandler(featureLoader));
router.post("/get", createGetHandler(featureLoader));
router.post("/create", createCreateHandler(featureLoader));
router.post("/update", createUpdateHandler(featureLoader));
router.post("/delete", createDeleteHandler(featureLoader));
router.post("/list", validatePathParams("projectPath"), createListHandler(featureLoader));
router.post("/get", validatePathParams("projectPath"), createGetHandler(featureLoader));
router.post("/create", validatePathParams("projectPath"), createCreateHandler(featureLoader));
router.post("/update", validatePathParams("projectPath"), createUpdateHandler(featureLoader));
router.post("/delete", validatePathParams("projectPath"), createDeleteHandler(featureLoader));
router.post("/agent-output", createAgentOutputHandler(featureLoader));
router.post("/generate-title", createGenerateTitleHandler());

View File

@@ -7,7 +7,6 @@ import {
FeatureLoader,
type Feature,
} from "../../../services/feature-loader.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
import { getErrorMessage, logError } from "../common.js";
export function createCreateHandler(featureLoader: FeatureLoader) {
@@ -28,20 +27,6 @@ export function createCreateHandler(featureLoader: FeatureLoader) {
return;
}
// Validate path is within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const created = await featureLoader.create(projectPath, feature);
res.json({ success: true, feature: created });
} catch (error) {

View File

@@ -5,7 +5,6 @@
import type { Request, Response } from "express";
import { FeatureLoader } from "../../../services/feature-loader.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
export function createDeleteHandler(featureLoader: FeatureLoader) {
return async (req: Request, res: Response): Promise<void> => {
@@ -25,20 +24,6 @@ export function createDeleteHandler(featureLoader: FeatureLoader) {
return;
}
// Validate path is within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const success = await featureLoader.delete(projectPath, featureId);
res.json({ success });
} catch (error) {

View File

@@ -5,7 +5,6 @@
import type { Request, Response } from "express";
import { FeatureLoader } from "../../../services/feature-loader.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
export function createGetHandler(featureLoader: FeatureLoader) {
return async (req: Request, res: Response): Promise<void> => {
@@ -25,20 +24,6 @@ export function createGetHandler(featureLoader: FeatureLoader) {
return;
}
// Validate path is within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const feature = await featureLoader.get(projectPath, featureId);
if (!feature) {
res.status(404).json({ success: false, error: "Feature not found" });

View File

@@ -4,7 +4,6 @@
import type { Request, Response } from "express";
import { FeatureLoader } from "../../../services/feature-loader.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
import { getErrorMessage, logError } from "../common.js";
export function createListHandler(featureLoader: FeatureLoader) {
@@ -19,20 +18,6 @@ export function createListHandler(featureLoader: FeatureLoader) {
return;
}
// Validate path is within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const features = await featureLoader.getAll(projectPath);
res.json({ success: true, features });
} catch (error) {

View File

@@ -8,7 +8,6 @@ import {
type Feature,
} from "../../../services/feature-loader.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
export function createUpdateHandler(featureLoader: FeatureLoader) {
return async (req: Request, res: Response): Promise<void> => {
@@ -27,20 +26,6 @@ export function createUpdateHandler(featureLoader: FeatureLoader) {
return;
}
// Validate path is within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const updated = await featureLoader.update(
projectPath,
featureId,

View File

@@ -3,14 +3,15 @@
*/
import { Router } from "express";
import { validatePathParams } from "../../middleware/validate-paths.js";
import { createDiffsHandler } from "./routes/diffs.js";
import { createFileDiffHandler } from "./routes/file-diff.js";
export function createGitRoutes(): Router {
const router = Router();
router.post("/diffs", createDiffsHandler());
router.post("/file-diff", createFileDiffHandler());
router.post("/diffs", validatePathParams("projectPath"), createDiffsHandler());
router.post("/file-diff", validatePathParams("projectPath", "filePath"), createFileDiffHandler());
return router;
}

View File

@@ -5,7 +5,6 @@
import type { Request, Response } from "express";
import { getErrorMessage, logError } from "../common.js";
import { getGitRepositoryDiffs } from "../../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
export function createDiffsHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -17,20 +16,6 @@ export function createDiffsHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
try {
const result = await getGitRepositoryDiffs(projectPath);
res.json({

View File

@@ -7,7 +7,6 @@ import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import { generateSyntheticDiffForNewFile } from "../../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -26,21 +25,6 @@ export function createFileDiffHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
validatePath(filePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
try {
// First check if the file is untracked
const { stdout: status } = await execAsync(

View File

@@ -14,6 +14,7 @@
import { Router } from "express";
import type { SettingsService } from "../../services/settings-service.js";
import { validatePathParams } from "../../middleware/validate-paths.js";
import { createGetGlobalHandler } from "./routes/get-global.js";
import { createUpdateGlobalHandler } from "./routes/update-global.js";
import { createGetCredentialsHandler } from "./routes/get-credentials.js";
@@ -57,8 +58,8 @@ export function createSettingsRoutes(settingsService: SettingsService): Router {
router.put("/credentials", createUpdateCredentialsHandler(settingsService));
// Project settings
router.post("/project", createGetProjectHandler(settingsService));
router.put("/project", createUpdateProjectHandler(settingsService));
router.post("/project", validatePathParams("projectPath"), createGetProjectHandler(settingsService));
router.put("/project", validatePathParams("projectPath"), createUpdateProjectHandler(settingsService));
// Migration from localStorage
router.post("/migrate", createMigrateHandler(settingsService));

View File

@@ -11,7 +11,6 @@
import type { Request, Response } from "express";
import type { SettingsService } from "../../../services/settings-service.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
/**
* Create handler factory for POST /api/settings/project
@@ -32,20 +31,6 @@ export function createGetProjectHandler(settingsService: SettingsService) {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const settings = await settingsService.getProjectSettings(projectPath);
res.json({

View File

@@ -12,7 +12,6 @@ import type { Request, Response } from "express";
import type { SettingsService } from "../../../services/settings-service.js";
import type { ProjectSettings } from "../../../types/settings.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
/**
* Create handler factory for PUT /api/settings/project
@@ -44,20 +43,6 @@ export function createUpdateProjectHandler(settingsService: SettingsService) {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const settings = await settingsService.updateProjectSettings(
projectPath,
updates

View File

@@ -4,6 +4,7 @@
import { Router } from "express";
import type { EventEmitter } from "../../lib/events.js";
import { validatePathParams } from "../../middleware/validate-paths.js";
import { createGenerateHandler } from "./routes/generate.js";
import { createStopHandler } from "./routes/stop.js";
import { createStatusHandler } from "./routes/status.js";
@@ -11,7 +12,7 @@ import { createStatusHandler } from "./routes/status.js";
export function createSuggestionsRoutes(events: EventEmitter): Router {
const router = Router();
router.post("/generate", createGenerateHandler(events));
router.post("/generate", validatePathParams("projectPath"), createGenerateHandler(events));
router.post("/stop", createStopHandler());
router.get("/status", createStatusHandler());

View File

@@ -12,7 +12,6 @@ import {
logError,
} from "../common.js";
import { generateSuggestions } from "../generate-suggestions.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const logger = createLogger("Suggestions");
@@ -29,20 +28,6 @@ export function createGenerateHandler(events: EventEmitter) {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const { isRunning } = getSuggestionsStatus();
if (isRunning) {
res.json({

View File

@@ -3,6 +3,7 @@
*/
import { Router } from "express";
import { validatePathParams } from "../../middleware/validate-paths.js";
import { createInfoHandler } from "./routes/info.js";
import { createStatusHandler } from "./routes/status.js";
import { createListHandler } from "./routes/list.js";
@@ -32,27 +33,27 @@ import { createListDevServersHandler } from "./routes/list-dev-servers.js";
export function createWorktreeRoutes(): Router {
const router = Router();
router.post("/info", createInfoHandler());
router.post("/status", createStatusHandler());
router.post("/info", validatePathParams("projectPath"), createInfoHandler());
router.post("/status", validatePathParams("projectPath"), createStatusHandler());
router.post("/list", createListHandler());
router.post("/diffs", createDiffsHandler());
router.post("/file-diff", createFileDiffHandler());
router.post("/merge", createMergeHandler());
router.post("/create", createCreateHandler());
router.post("/delete", createDeleteHandler());
router.post("/diffs", validatePathParams("projectPath"), createDiffsHandler());
router.post("/file-diff", validatePathParams("projectPath", "filePath"), createFileDiffHandler());
router.post("/merge", validatePathParams("projectPath"), createMergeHandler());
router.post("/create", validatePathParams("projectPath"), createCreateHandler());
router.post("/delete", validatePathParams("projectPath", "worktreePath"), createDeleteHandler());
router.post("/create-pr", createCreatePRHandler());
router.post("/pr-info", createPRInfoHandler());
router.post("/commit", createCommitHandler());
router.post("/push", createPushHandler());
router.post("/pull", createPullHandler());
router.post("/commit", validatePathParams("worktreePath"), createCommitHandler());
router.post("/push", validatePathParams("worktreePath"), createPushHandler());
router.post("/pull", validatePathParams("worktreePath"), createPullHandler());
router.post("/checkout-branch", createCheckoutBranchHandler());
router.post("/list-branches", createListBranchesHandler());
router.post("/list-branches", validatePathParams("worktreePath"), createListBranchesHandler());
router.post("/switch-branch", createSwitchBranchHandler());
router.post("/open-in-editor", createOpenInEditorHandler());
router.post("/open-in-editor", validatePathParams("worktreePath"), createOpenInEditorHandler());
router.get("/default-editor", createGetDefaultEditorHandler());
router.post("/init-git", createInitGitHandler());
router.post("/init-git", validatePathParams("projectPath"), createInitGitHandler());
router.post("/migrate", createMigrateHandler());
router.post("/start-dev", createStartDevHandler());
router.post("/start-dev", validatePathParams("projectPath", "worktreePath"), createStartDevHandler());
router.post("/stop-dev", createStopDevHandler());
router.post("/list-dev-servers", createListDevServersHandler());

View File

@@ -6,7 +6,6 @@ import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -26,20 +25,6 @@ export function createCommitHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(worktreePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Check for uncommitted changes
const { stdout: status } = await execAsync("git status --porcelain", {
cwd: worktreePath,

View File

@@ -20,7 +20,6 @@ import {
ensureInitialCommit,
} from "../common.js";
import { trackBranch } from "./branch-tracking.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -92,20 +91,6 @@ export function createCreateHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
if (!(await isGitRepo(projectPath))) {
res.status(400).json({
success: false,

View File

@@ -6,7 +6,6 @@ import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { isGitRepo, getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -27,21 +26,6 @@ export function createDeleteHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
validatePath(worktreePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
if (!(await isGitRepo(projectPath))) {
res.status(400).json({
success: false,

View File

@@ -7,7 +7,6 @@ import path from "path";
import fs from "fs/promises";
import { getErrorMessage, logError } from "../common.js";
import { getGitRepositoryDiffs } from "../../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
export function createDiffsHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -27,20 +26,6 @@ export function createDiffsHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Git worktrees are stored in project directory
const worktreePath = path.join(projectPath, ".worktrees", featureId);

View File

@@ -9,7 +9,6 @@ import path from "path";
import fs from "fs/promises";
import { getErrorMessage, logError } from "../common.js";
import { generateSyntheticDiffForNewFile } from "../../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -30,21 +29,6 @@ export function createFileDiffHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
validatePath(filePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Git worktrees are stored in project directory
const worktreePath = path.join(projectPath, ".worktrees", featureId);

View File

@@ -8,7 +8,6 @@ import { promisify } from "util";
import path from "path";
import fs from "fs/promises";
import { getErrorMessage, logError, normalizePath } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -30,20 +29,6 @@ export function createInfoHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Check if worktree exists (git worktrees are stored in project directory)
const worktreePath = path.join(projectPath, ".worktrees", featureId);
try {

View File

@@ -8,7 +8,6 @@ import { promisify } from "util";
import { existsSync } from "fs";
import { join } from "path";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -27,20 +26,6 @@ export function createInitGitHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Check if .git already exists
const gitDirPath = join(projectPath, ".git");
if (existsSync(gitDirPath)) {

View File

@@ -6,7 +6,6 @@ import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logWorktreeError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -31,20 +30,6 @@ export function createListBranchesHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(worktreePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Get current branch
const { stdout: currentBranchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",

View File

@@ -7,7 +7,6 @@ import { exec } from "child_process";
import { promisify } from "util";
import path from "path";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -30,20 +29,6 @@ export function createMergeHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const branchName = `feature/${featureId}`;
// Git worktrees are stored in project directory
const worktreePath = path.join(projectPath, ".worktrees", featureId);

View File

@@ -7,7 +7,6 @@ import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -109,20 +108,6 @@ export function createOpenInEditorHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(worktreePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const editor = await detectDefaultEditor();
try {

View File

@@ -6,7 +6,6 @@ import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -25,20 +24,6 @@ export function createPullHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(worktreePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Get current branch name
const { stdout: branchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",

View File

@@ -6,7 +6,6 @@ import type { Request, Response } from "express";
import { exec } from "child_process";
import { promisify } from "util";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -26,20 +25,6 @@ export function createPushHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(worktreePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Get branch name
const { stdout: branchOutput } = await execAsync(
"git rev-parse --abbrev-ref HEAD",

View File

@@ -9,7 +9,6 @@
import type { Request, Response } from "express";
import { getDevServerService } from "../../../services/dev-server-service.js";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
export function createStartDevHandler() {
return async (req: Request, res: Response): Promise<void> => {
@@ -35,21 +34,6 @@ export function createStartDevHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
validatePath(worktreePath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
const devServerService = getDevServerService();
const result = await devServerService.startDevServer(projectPath, worktreePath);

View File

@@ -8,7 +8,6 @@ import { promisify } from "util";
import path from "path";
import fs from "fs/promises";
import { getErrorMessage, logError } from "../common.js";
import { validatePath, PathNotAllowedError } from "../../../lib/security.js";
const execAsync = promisify(exec);
@@ -30,20 +29,6 @@ export function createStatusHandler() {
return;
}
// Validate paths are within ALLOWED_ROOT_DIRECTORY
try {
validatePath(projectPath);
} catch (error) {
if (error instanceof PathNotAllowedError) {
res.status(403).json({
success: false,
error: error.message,
});
return;
}
throw error;
}
// Git worktrees are stored in project directory
const worktreePath = path.join(projectPath, ".worktrees", featureId);