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
JavaScript
/**
* @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);
}
};