ken-you-code
Version:
Connect your codebase to Kimi: Ultra-fast AI code analysis with Kimi-K2 model via MCP
63 lines (62 loc) • 2.4 kB
JavaScript
import { readFile, stat } from 'fs/promises';
import path from 'path';
import { config } from '../config/index.js';
export class FileOperations {
static isPathSafe(filePath) {
const resolvedPath = path.resolve(filePath);
// Check if workspace root is configured and enforce it
if (config.workspaceRoot) {
const workspaceRoot = path.resolve(config.workspaceRoot);
if (!resolvedPath.startsWith(workspaceRoot)) {
return false;
}
}
// Check for forbidden paths
const forbiddenPaths = ['.env', '.git/', 'node_modules/', '__pycache__/'];
for (const forbidden of forbiddenPaths) {
if (resolvedPath.includes(forbidden)) {
return false;
}
}
return true;
}
static isExtensionAllowed(filePath) {
const ext = path.extname(filePath).toLowerCase();
return config.allowedExtensions.includes(ext);
}
static async readFileSecurely(filePath) {
// Validate path safety
if (!this.isPathSafe(filePath)) {
throw new Error(`Access denied: Path '${filePath}' is not allowed`);
}
// Validate file extension
if (!this.isExtensionAllowed(filePath)) {
throw new Error(`File type not allowed: ${path.extname(filePath)}`);
}
try {
// Check file exists and get stats
const fileStats = await stat(filePath);
if (!fileStats.isFile()) {
throw new Error(`Path is not a file: ${filePath}`);
}
// Check file size
const fileSizeMB = fileStats.size / (1024 * 1024);
if (fileSizeMB > config.maxFileSizeMB) {
throw new Error(`File too large: ${fileSizeMB.toFixed(2)}MB (max: ${config.maxFileSizeMB}MB)`);
}
// Read file content
const content = await readFile(filePath, 'utf-8');
return {
content,
size: fileStats.size,
extension: path.extname(filePath),
};
}
catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to read file '${filePath}': ${error.message}`);
}
throw new Error(`Failed to read file '${filePath}': Unknown error`);
}
}
}