feat(mcp): 添加 CCW_DISABLE_SANDBOX 环境变量支持禁用工作空间访问限制

- 在 path-validator.ts 中添加 isSandboxDisabled() 函数
- 修改 validatePath() 在沙箱禁用时跳过路径限制检查
- MCP server 启动日志显示沙箱状态
- /api/mcp-install-ccw API 支持 disableSandbox 参数
- Dashboard UI 添加禁用沙箱的复选框选项
- 添加中英文 i18n 翻译支持
This commit is contained in:
catlog22
2026-01-20 11:50:23 +08:00
parent eea859dd6f
commit c1d12384c3
6 changed files with 69 additions and 11 deletions

View File

@@ -14,6 +14,16 @@ import { constants } from 'fs';
// Environment variable configuration
const ENV_PROJECT_ROOT = 'CCW_PROJECT_ROOT';
const ENV_ALLOWED_DIRS = 'CCW_ALLOWED_DIRS';
const ENV_DISABLE_SANDBOX = 'CCW_DISABLE_SANDBOX';
/**
* Check if sandbox mode is disabled
* When disabled, path validation allows access to any directory
*/
export function isSandboxDisabled(): boolean {
const value = process.env[ENV_DISABLE_SANDBOX];
return value === '1' || value?.toLowerCase() === 'true';
}
/**
* Get project root directory
@@ -100,6 +110,7 @@ export async function validatePath(
mustExist?: boolean;
} = {}
): Promise<string> {
const sandboxDisabled = isSandboxDisabled();
const allowedDirs = options.allowedDirectories || getAllowedDirectories();
// 1. Resolve to absolute path
@@ -108,8 +119,8 @@ export async function validatePath(
: resolve(getProjectRoot(), filePath);
const normalizedPath = normalizePath(absolutePath);
// 2. Initial sandbox check
if (!isPathWithinAllowedDirectories(normalizedPath, allowedDirs)) {
// 2. Initial sandbox check (skip if sandbox is disabled)
if (!sandboxDisabled && !isPathWithinAllowedDirectories(normalizedPath, allowedDirs)) {
throw new Error(
`Access denied: path "${normalizedPath}" is outside allowed directories. ` +
`Allowed: [${allowedDirs.join(', ')}]`
@@ -121,7 +132,8 @@ export async function validatePath(
const realPath = await realpath(absolutePath);
const normalizedReal = normalizePath(realPath);
if (!isPathWithinAllowedDirectories(normalizedReal, allowedDirs)) {
// Skip sandbox check for symlink target if sandbox is disabled
if (!sandboxDisabled && !isPathWithinAllowedDirectories(normalizedReal, allowedDirs)) {
throw new Error(
`Access denied: symlink target "${normalizedReal}" is outside allowed directories`
);
@@ -135,13 +147,13 @@ export async function validatePath(
throw new Error(`File not found: ${absolutePath}`);
}
// Validate parent directory's real path
// Validate parent directory's real path (skip if sandbox is disabled)
const parentDir = resolve(absolutePath, '..');
try {
const realParent = await realpath(parentDir);
const normalizedParent = normalizePath(realParent);
if (!isPathWithinAllowedDirectories(normalizedParent, allowedDirs)) {
if (!sandboxDisabled && !isPathWithinAllowedDirectories(normalizedParent, allowedDirs)) {
throw new Error(
`Access denied: parent directory "${normalizedParent}" is outside allowed directories`
);