refactor: implement ALLOWED_ROOT_DIRECTORY security and fix path validation

This commit consolidates directory security from two environment variables
(WORKSPACE_DIR, ALLOWED_PROJECT_DIRS) into a single ALLOWED_ROOT_DIRECTORY variable
while maintaining backward compatibility.

Changes:
- Re-enabled path validation in security.ts (was previously disabled)
- Implemented isPathAllowed() to check ALLOWED_ROOT_DIRECTORY with DATA_DIR exception
- Added backward compatibility for legacy ALLOWED_PROJECT_DIRS and WORKSPACE_DIR
- Implemented path traversal protection via isPathWithinDirectory() helper
- Added PathNotAllowedError custom exception for security violations
- Updated all FS route endpoints to validate paths and return 403 on violation
- Updated template clone endpoint to validate project paths
- Updated workspace config endpoints to use ALLOWED_ROOT_DIRECTORY
- Fixed stat() response property access bug in project-init.ts
- Updated security tests to expect actual validation behavior

Security improvements:
- Path validation now enforced at all layers (routes, project init, agent services)
- appData directory (DATA_DIR) always allowed for settings/credentials
- Backward compatible with existing ALLOWED_PROJECT_DIRS/WORKSPACE_DIR configurations
- Protection against path traversal attacks

Backend test results: 654/654 passing 

🤖 Generated with Claude Code

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Test User
2025-12-20 15:59:32 -05:00
parent 7d0656bb14
commit 8ff4b5912a
25 changed files with 424 additions and 72 deletions

View File

@@ -6,7 +6,7 @@ import type { Request, Response } from "express";
import { spawn } from "child_process";
import path from "path";
import fs from "fs/promises";
import { addAllowedPath } from "../../../lib/security.js";
import { addAllowedPath, isPathAllowed, PathNotAllowedError } from "../../../lib/security.js";
import { logger, getErrorMessage, logError } from "../common.js";
export function createCloneHandler() {
@@ -63,6 +63,24 @@ export function createCloneHandler() {
return;
}
// Validate that parent directory is within allowed root directory
if (!isPathAllowed(resolvedParent)) {
res.status(403).json({
success: false,
error: `Parent directory not allowed: ${parentDir}. Must be within ALLOWED_ROOT_DIRECTORY.`,
});
return;
}
// Validate that project path will be within allowed root directory
if (!isPathAllowed(resolvedProject)) {
res.status(403).json({
success: false,
error: `Project path not allowed: ${projectPath}. Must be within ALLOWED_ROOT_DIRECTORY.`,
});
return;
}
// Check if directory already exists
try {
await fs.access(projectPath);