@sirmrmarty/n8n-nodes-tmux-orchestrator
Version:
n8n nodes for orchestrating Claude AI agents through tmux sessions
280 lines • 11.1 kB
JavaScript
;
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