豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit a54ce63

Browse files
author
Corlzee
committed
feat: Add read-only directory protection
- Check readOnlyDirectories config before write operations - Protect system directories from modification - Clear error messages for protected paths - Empty array (default) maintains original behavior Prevents accidental modification of critical system files.
1 parent 94d8e73 commit a54ce63

File tree

1 file changed

+47
-6
lines changed

1 file changed

+47
-6
lines changed

src/tools/filesystem.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,47 @@ async function isPathAllowed(pathToCheck: string): Promise<boolean> {
214214
return isAllowed;
215215
}
216216

217+
/**
218+
* Check if a path is within a read-only directory
219+
* @param checkPath The path to check
220+
* @returns Promise<boolean> True if the path is read-only
221+
*/
222+
async function isPathReadOnly(checkPath: string): Promise<boolean> {
223+
const config = await configManager.getConfig();
224+
const readOnlyDirs = config.readOnlyDirectories || [];
225+
226+
if (readOnlyDirs.length === 0) {
227+
return false; // No read-only directories configured
228+
}
229+
230+
const normalizedCheckPath = path.normalize(checkPath).toLowerCase();
231+
232+
for (const dir of readOnlyDirs) {
233+
const expandedDir = expandHome(dir);
234+
const normalizedDir = path.normalize(expandedDir).toLowerCase();
235+
236+
// Check if the path is within the read-only directory
237+
if (normalizedCheckPath === normalizedDir ||
238+
normalizedCheckPath.startsWith(normalizedDir + path.sep)) {
239+
return true;
240+
}
241+
}
242+
243+
return false;
244+
}
245+
217246
/**
218247
* Validates a path to ensure it can be accessed or created.
219248
* For existing paths, returns the real path (resolving symlinks).
220249
* For non-existent paths, validates parent directories to ensure they exist.
250+
* For write operations, also checks if the path is read-only.
221251
*
222252
* @param requestedPath The path to validate
253+
* @param isWriteOperation Whether this is a write operation (default: false)
223254
* @returns Promise<string> The validated path
224-
* @throws Error if the path or its parent directories don't exist or if the path is not allowed
255+
* @throws Error if the path or its parent directories don't exist or if the path is not allowed or read-only
225256
*/
226-
export async function validatePath(requestedPath: string): Promise<string> {
257+
export async function validatePath(requestedPath: string, isWriteOperation: boolean = false): Promise<string> {
227258
const validationOperation = async (): Promise<string> => {
228259
// Expand home directory if present
229260
const expandedPath = expandHome(requestedPath);
@@ -243,6 +274,16 @@ export async function validatePath(requestedPath: string): Promise<string> {
243274
throw new Error(`Path not allowed: ${requestedPath}. Must be within one of these directories: ${(await getAllowedDirs()).join(', ')}`);
244275
}
245276

277+
// Check if path is read-only for write operations
278+
if (isWriteOperation && await isPathReadOnly(absolute)) {
279+
capture('server_path_validation_error', {
280+
error: 'Path is read-only',
281+
operation: 'write'
282+
});
283+
284+
throw new Error(`Path is read-only: ${requestedPath}. This directory is protected from modifications.`);
285+
}
286+
246287
// Check if path exists
247288
try {
248289
const stats = await fs.stat(absolute);
@@ -828,7 +869,7 @@ function splitLinesPreservingEndings(content: string): string[] {
828869
}
829870

830871
export async function writeFile(filePath: string, content: string, mode: 'rewrite' | 'append' = 'rewrite'): Promise<void> {
831-
const validPath = await validatePath(filePath);
872+
const validPath = await validatePath(filePath, true); // Mark as write operation
832873

833874
// Get file extension for telemetry
834875
const fileExtension = getFileExtension(validPath);
@@ -886,7 +927,7 @@ export async function readMultipleFiles(paths: string[]): Promise<MultiFileResul
886927
}
887928

888929
export async function createDirectory(dirPath: string): Promise<void> {
889-
const validPath = await validatePath(dirPath);
930+
const validPath = await validatePath(dirPath, true); // Creating directory is a write operation
890931
await fs.mkdir(validPath, { recursive: true });
891932
}
892933

@@ -897,8 +938,8 @@ export async function listDirectory(dirPath: string): Promise<string[]> {
897938
}
898939

899940
export async function moveFile(sourcePath: string, destinationPath: string): Promise<void> {
900-
const validSourcePath = await validatePath(sourcePath);
901-
const validDestPath = await validatePath(destinationPath);
941+
const validSourcePath = await validatePath(sourcePath, true); // Source needs write permission (to delete)
942+
const validDestPath = await validatePath(destinationPath, true); // Destination needs write permission
902943
await fs.rename(validSourcePath, validDestPath);
903944
}
904945

0 commit comments

Comments
 (0)