UNPKG

@sirmrmarty/n8n-nodes-tmux-orchestrator

Version:

n8n nodes for orchestrating Claude AI agents through tmux sessions

280 lines 11.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SecureTempFileManager = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const crypto = __importStar(require("crypto")); const resourceManager_1 = require("./resourceManager"); class SecureTempFileManager { static async createSecureTempFile(content, options = {}) { try { if (content == null) { throw new Error('Content cannot be null or undefined'); } if (typeof content === 'string' && content.length > 10 * 1024 * 1024) { throw new Error('Content too large (max 10MB)'); } if (Buffer.isBuffer(content) && content.length > 10 * 1024 * 1024) { throw new Error('Content too large (max 10MB)'); } if (this.activeTempFiles.size >= this.MAX_TEMP_FILES) { throw new Error('Too many active temporary files - cleanup required'); } const tempDir = options.directory || this.DEFAULT_TEMP_DIR; await this.ensureSecureDirectory(tempDir); const filename = this.generateSecureFilename(options.prefix, options.suffix); const filepath = path.join(tempDir, filename); const resolvedPath = path.resolve(filepath); const resolvedDir = path.resolve(tempDir); if (!resolvedPath.startsWith(resolvedDir)) { throw new Error('Path traversal attempt detected'); } const mode = options.mode || this.DEFAULT_MODE; await fs.promises.writeFile(filepath, content, { mode }); const stats = await fs.promises.stat(filepath); if ((stats.mode & 0o777) !== mode) { await fs.promises.unlink(filepath); throw new Error('Failed to set secure file permissions'); } let isValid = true; const cleanup = async () => { if (!isValid) return; try { await fs.promises.unlink(filepath); this.activeTempFiles.delete(filepath); isValid = false; } catch (error) { if (error.code !== 'ENOENT') { console.warn(`Failed to cleanup temp file ${filepath}: ${error.message}`); } isValid = false; } }; const fileInfo = { path: filepath, cleanup, isValid: () => isValid, getStats: () => { try { return fs.statSync(filepath); } catch { isValid = false; return null; } } }; this.activeTempFiles.set(filepath, fileInfo); const maxAge = options.maxAge || this.DEFAULT_MAX_AGE; setTimeout(async () => { if (isValid) { await cleanup(); } }, maxAge); if (!this.cleanupScheduled) { this.scheduleGlobalCleanup(options.keepOnExit !== true); } return fileInfo; } catch (error) { throw new Error(`Failed to create secure temporary file: ${error.message}`); } } static async createSecureTempDirectory(options = {}) { try { const tempDir = options.directory || this.DEFAULT_TEMP_DIR; await this.ensureSecureDirectory(tempDir); const dirname = this.generateSecureFilename(options.prefix || 'dir', ''); const dirpath = path.join(tempDir, dirname); const resolvedPath = path.resolve(dirpath); const resolvedDir = path.resolve(tempDir); if (!resolvedPath.startsWith(resolvedDir)) { throw new Error('Path traversal attempt detected'); } const mode = options.mode || this.DEFAULT_DIR_MODE; await fs.promises.mkdir(dirpath, { mode }); const stats = await fs.promises.stat(dirpath); if ((stats.mode & 0o777) !== mode) { await fs.promises.rmdir(dirpath); throw new Error('Failed to set secure directory permissions'); } const cleanup = async () => { try { await fs.promises.rmdir(dirpath, { recursive: true }); } catch (error) { if (error.code !== 'ENOENT') { console.warn(`Failed to cleanup temp directory ${dirpath}: ${error.message}`); } } }; const maxAge = options.maxAge || this.DEFAULT_MAX_AGE; setTimeout(cleanup, maxAge); return { path: dirpath, cleanup }; } catch (error) { throw new Error(`Failed to create secure temporary directory: ${error.message}`); } } static async getSecureTempDir(customDir) { const tempDir = customDir || this.DEFAULT_TEMP_DIR; await this.ensureSecureDirectory(tempDir); return tempDir; } static async cleanupAll() { const cleanupPromises = Array.from(this.activeTempFiles.values()).map(fileInfo => fileInfo.cleanup()); await Promise.allSettled(cleanupPromises); this.activeTempFiles.clear(); } static getStats() { let totalSize = 0; let oldestFileAge = 0; const now = Date.now(); for (const fileInfo of this.activeTempFiles.values()) { if (fileInfo.isValid()) { const stats = fileInfo.getStats(); if (stats) { totalSize += stats.size; const age = now - stats.birthtimeMs; oldestFileAge = Math.max(oldestFileAge, age); } } } return { activeFiles: this.activeTempFiles.size, oldestFileAge, totalSize }; } static async ensureSecureDirectory(dirPath) { try { const stats = await fs.promises.stat(dirPath); if (!stats.isDirectory()) { throw new Error(`Path exists but is not a directory: ${dirPath}`); } const mode = stats.mode & 0o777; if (mode !== this.DEFAULT_DIR_MODE) { await fs.promises.chmod(dirPath, this.DEFAULT_DIR_MODE); } } catch (error) { if (error.code === 'ENOENT') { await fs.promises.mkdir(dirPath, { recursive: true, mode: this.DEFAULT_DIR_MODE }); } else { throw error; } } } static generateSecureFilename(prefix, suffix) { const timestamp = Date.now(); const randomBytes = crypto.randomBytes(16).toString('hex'); const pid = process.pid; const parts = [ prefix || 'secure', timestamp, pid, randomBytes, suffix || 'tmp' ].filter(Boolean); return parts.join('_'); } static scheduleGlobalCleanup(cleanupOnExit) { if (this.cleanupScheduled) return; this.cleanupScheduled = true; if (cleanupOnExit) { process.on('exit', () => { for (const fileInfo of this.activeTempFiles.values()) { try { if (fileInfo.isValid() && fs.existsSync(fileInfo.path)) { fs.unlinkSync(fileInfo.path); } } catch (error) { } } }); ['SIGTERM', 'SIGINT', 'SIGHUP'].forEach(signal => { process.on(signal, async () => { await this.cleanupAll(); process.exit(0); }); }); } resourceManager_1.resourceManager.createInterval(async () => { try { await this.cleanupStaleFiles(); } catch (error) { console.warn('Failed to cleanup stale temporary files:', error.message); } }, 5 * 60 * 1000, 'Secure temp file cleanup'); } static async cleanupStaleFiles() { const toDelete = []; for (const [filepath, fileInfo] of this.activeTempFiles.entries()) { if (!fileInfo.isValid()) { toDelete.push(filepath); } else { const stats = fileInfo.getStats(); if (!stats) { toDelete.push(filepath); } } } for (const filepath of toDelete) { this.activeTempFiles.delete(filepath); } } } exports.SecureTempFileManager = SecureTempFileManager; SecureTempFileManager.DEFAULT_TEMP_DIR = process.platform === 'win32' ? path.join(process.env.TEMP || process.env.TMP || 'C:\\tmp', 'n8n-secure') : '/var/tmp/n8n-secure'; SecureTempFileManager.DEFAULT_MODE = 0o600; SecureTempFileManager.DEFAULT_DIR_MODE = 0o700; SecureTempFileManager.MAX_TEMP_FILES = 1000; SecureTempFileManager.DEFAULT_MAX_AGE = 30 * 60 * 1000; SecureTempFileManager.activeTempFiles = new Map(); SecureTempFileManager.cleanupScheduled = false; //# sourceMappingURL=secureTempFile.js.map