UNPKG

atlas-mcp-server

Version:

ATLAS (Adaptive Task & Logic Automation System): An MCP server enabling LLM agents to manage projects, tasks, and knowledge via a Neo4j-backed, three-tier architecture. Facilitates complex workflow automation and project management through LLM Agents.

105 lines (104 loc) 5.13 kB
/** * @fileoverview Provides utility functions for the backup and restore service, * including secure path resolution and backup rotation management. * @module src/services/neo4j/backupRestoreService/backupUtils */ import { existsSync, readdirSync, rmSync } from "fs"; import { stat } from "fs/promises"; import path from "path"; import { config } from "../../../config/index.js"; import { logger, requestContextService } from "../../../utils/index.js"; // Define the validated root backup path from config export const validatedBackupRoot = config.backup.backupPath; /** * Securely resolves a path against a base directory and ensures it stays within that base. * @param basePath The absolute, validated base path. * @param targetPath The relative or absolute path to resolve. * @returns The resolved absolute path if it's within the base path, otherwise null. */ export const secureResolve = (basePath, targetPath) => { const resolvedTarget = path.resolve(basePath, targetPath); if (resolvedTarget.startsWith(basePath + path.sep) || resolvedTarget === basePath) { return resolvedTarget; } const errorContext = requestContextService.createRequestContext({ operation: "secureResolve.PathViolation", targetPath, resolvedTarget, basePath, }); logger.error(`Security Violation: Path "${targetPath}" resolves to "${resolvedTarget}", which is outside the allowed base directory "${basePath}".`, new Error("Path security violation"), errorContext); return null; }; /** * Manages backup rotation, deleting the oldest backups if the count exceeds the limit. */ export const manageBackupRotation = async () => { const maxBackups = config.backup.maxBackups; const operationName = "manageBackupRotation"; const baseContext = requestContextService.createRequestContext({ operation: operationName, }); if (!existsSync(validatedBackupRoot)) { logger.warning(`Backup root directory does not exist: ${validatedBackupRoot}. Skipping rotation.`, { ...baseContext, pathChecked: validatedBackupRoot }); return; } try { logger.debug(`Checking backup rotation in ${validatedBackupRoot}. Max backups: ${maxBackups}`, baseContext); const dirNames = readdirSync(validatedBackupRoot); const processedDirs = await Promise.all(dirNames.map(async (name) => { const potentialDirPath = secureResolve(validatedBackupRoot, name); if (!potentialDirPath) return null; try { const stats = await stat(potentialDirPath); if (stats.isDirectory()) { return { path: potentialDirPath, time: stats.mtime.getTime() }; } } catch (statError) { if (statError.code !== "ENOENT") { logger.warning(`Could not stat potential backup directory ${potentialDirPath}: ${statError.message}. Skipping.`, { ...baseContext, path: potentialDirPath, errorCode: statError.code, }); } } return null; })); const validBackupDirs = processedDirs .filter((dir) => dir !== null) .sort((a, b) => a.time - b.time); const backupsToDeleteCount = validBackupDirs.length - maxBackups; if (backupsToDeleteCount > 0) { logger.info(`Found ${validBackupDirs.length} valid backups. Deleting ${backupsToDeleteCount} oldest backups to maintain limit of ${maxBackups}.`, baseContext); for (let i = 0; i < backupsToDeleteCount; i++) { const dirToDelete = validBackupDirs[i].path; if (!dirToDelete.startsWith(validatedBackupRoot + path.sep)) { logger.error(`Security Error: Attempting to delete directory outside backup root: ${dirToDelete}. Aborting deletion.`, new Error("Backup deletion security violation"), { ...baseContext, dirToDelete }); continue; } try { rmSync(dirToDelete, { recursive: true, force: true }); logger.info(`Deleted old backup directory: ${dirToDelete}`, { ...baseContext, deletedPath: dirToDelete, }); } catch (rmError) { const errorMsg = rmError instanceof Error ? rmError.message : String(rmError); logger.error(`Failed to delete old backup directory ${dirToDelete}: ${errorMsg}`, rmError, { ...baseContext, dirToDelete }); } } } else { logger.debug(`Backup count (${validBackupDirs.length}) is within the limit (${maxBackups}). No rotation needed.`, baseContext); } } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error(`Error during backup rotation management: ${errorMsg}`, error, baseContext); } };