import path from "path";
import fs from "fs-extra";
export class SecurityManager {
constructor(allowedDirectories) {
this.allowedDirectories = allowedDirectories.map(dir => path.resolve(dir));
this.maxFileSize = 100 * 1024 * 1024; // 100MB
this.backupDir = path.join(process.cwd(), '.tafa-backups');
this.ensureBackupDir();
}
ensureBackupDir() {
if (!fs.existsSync(this.backupDir)) {
fs.mkdirSync(this.backupDir, { recursive: true });
}
}
validatePath(filePath) {
const resolvedPath = path.resolve(filePath);
// Check if path is within allowed directories
const isAllowed = this.allowedDirectories.some(allowedDir => {
const relative = path.relative(allowedDir, resolvedPath);
return !relative || (!relative.startsWith('..') && !path.isAbsolute(relative));
});
if (!isAllowed) {
throw new Error(`❌ Access denied: Path ${resolvedPath} is outside allowed directories`);
}
return resolvedPath;
}
async validateFileSize(filePath) {
try {
const stats = await fs.stat(filePath);
if (stats.size > this.maxFileSize) {
throw new Error(`❌ File too large: ${filePath} exceeds ${this.maxFileSize / 1024 / 1024}MB limit`);
}
} catch (error) {
// File doesn't exist, which is ok for write operations
if (error.code !== 'ENOENT') {
throw error;
}
}
}
async createBackup(filePath) {
try {
if (await fs.pathExists(filePath)) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupFileName = `${path.basename(filePath)}_${timestamp}`;
const backupPath = path.join(this.backupDir, backupFileName);
await fs.copy(filePath, backupPath);
return backupPath;
}
} catch (error) {
console.warn(`⚠️ Warning: Could not create backup for ${filePath}: ${error.message}`);
}
return null;
}
sanitizeFileName(fileName) {
// Remove or replace dangerous characters
return fileName.replace(/[<>:"/\\|?*\x00-\x1f]/g, '_');
}
async checkPermissions(filePath, operation) {
try {
const resolvedPath = path.resolve(filePath);
const dir = path.dirname(resolvedPath);
// Check if we can access the directory
await fs.access(dir, fs.constants.F_OK);
// Check specific permissions based on operation
switch (operation) {
case 'read':
await fs.access(resolvedPath, fs.constants.R_OK);
break;
case 'write':
// Check if file exists, if not check directory write permissions
if (await fs.pathExists(resolvedPath)) {
await fs.access(resolvedPath, fs.constants.W_OK);
} else {
await fs.access(dir, fs.constants.W_OK);
}
break;
case 'delete':
await fs.access(resolvedPath, fs.constants.W_OK);
break;
}
} catch (error) {
throw new Error(`❌ Permission denied: Cannot ${operation} ${filePath}`);
}
}
}