p2pclaw-api / packages /api /src /services /IsolateSandbox.js
Frank-Agnuxo's picture
feat: P2PCLAW API for HF Spaces — ChessBoard Reasoning Engine + full API
e92be04
import { spawn } from 'node:child_process';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import vm from 'node:vm';
/**
* IsolateSandbox
* Provides a secure, containerized environment for executing untrusted agent code.
* Primary: Docker containers.
* Fallback: Node.js `vm` module (lightweight isolation).
*/
export class IsolateSandbox {
constructor(workDir = './temp_sandbox') {
this.workDir = path.resolve(workDir);
this.dockerAvailable = null; // null = unchecked
}
async init() {
await fs.mkdir(this.workDir, { recursive: true });
// Pre-check Docker availability
try {
await new Promise((resolve, reject) => {
const p = spawn('docker', ['version'], { stdio: 'ignore' });
p.on('close', code => code === 0 ? resolve() : reject());
p.on('error', reject);
});
this.dockerAvailable = true;
console.log('[SANDBOX] Docker available. Using containerized execution.');
} catch {
this.dockerAvailable = false;
console.warn('[SANDBOX] Docker unavailable. Using vm fallback (lower isolation).');
}
}
/**
* Executes arbitrary code in a Docker container (or vm fallback).
* Supports JavaScript and Python.
*/
async execute(code, options = {}) {
const lang = options.language || this._detectLanguage(code);
options._lang = lang;
if (this.dockerAvailable) {
return this._executeDocker(code, options);
}
if (lang === 'python') {
return this._executePythonFallback(code, options);
}
return this._executeVm(code, options);
}
/**
* Detect language from code content.
*/
_detectLanguage(code) {
const trimmed = code.trim();
if (trimmed.startsWith('#!/usr/bin/env python') || trimmed.startsWith('#!/usr/bin/python')) return 'python';
if (trimmed.match(/^(import |from |def |class |print\()/m)) return 'python';
return 'javascript';
}
/**
* Docker-based execution (high isolation). Supports JS + Python.
*/
async _executeDocker(code, options = {}) {
const runId = crypto.randomBytes(8).toString('hex');
const runFolder = path.join(this.workDir, runId);
await fs.mkdir(runFolder);
const lang = options._lang || 'javascript';
const ext = lang === 'python' ? '.py' : '.js';
const image = lang === 'python' ? 'python:3.11-slim' : 'node:18-slim';
const cmd = lang === 'python' ? ['python', `script${ext}`] : ['node', `script${ext}`];
const scriptPath = path.join(runFolder, `script${ext}`);
await fs.writeFile(scriptPath, code);
const memoryLimit = options.memory || '128m';
const cpuLimit = options.cpus || '0.5';
const timeout = options.timeout || 10000;
return new Promise((resolve) => {
const dockerArgs = [
'run', '--rm',
'--name', `p2pclaw-sandbox-${runId}`,
'--memory', memoryLimit,
'--cpus', cpuLimit,
'--network', 'none',
'-v', `${path.resolve(runFolder)}:/app`,
'-w', '/app',
image, ...cmd
];
const proc = spawn('docker', dockerArgs);
let stdout = '';
let stderr = '';
const timer = setTimeout(() => {
spawn('docker', ['stop', `p2pclaw-sandbox-${runId}`]);
resolve({ success: false, error: 'TIMEOUT', language: lang, stdout, stderr: stderr + '\nExecution timed out.' });
}, timeout);
proc.stdout.on('data', d => stdout += d.toString());
proc.stderr.on('data', d => stderr += d.toString());
proc.on('close', async (code) => {
clearTimeout(timer);
try { await fs.rm(runFolder, { recursive: true, force: true }); } catch {}
resolve({ success: code === 0, exitCode: code, language: lang, stdout, stderr });
});
});
}
/**
* vm module-based execution (processes fallback for local dev).
*/
async _executeVm(code, options = {}) {
const timeout = options.timeout || 5000;
const logs = [];
try {
const sandbox = {
console: {
log: (...args) => logs.push(args.join(' ')),
error: (...args) => logs.push('[ERR] ' + args.join(' '))
},
Math, Date, JSON, Array, Object, Number, String, Boolean
};
vm.createContext(sandbox);
vm.runInContext(code, sandbox, { timeout });
return {
success: true,
exitCode: 0,
stdout: logs.join('\n'),
stderr: ''
};
} catch (err) {
return {
success: false,
exitCode: 1,
stdout: logs.join('\n'),
stderr: err.message
};
}
}
/**
* Python fallback — try local Python interpreter if Docker unavailable.
*/
async _executePythonFallback(code, options = {}) {
const timeout = options.timeout || 10000;
const runId = crypto.randomBytes(8).toString('hex');
const tmpFile = path.join(this.workDir, `py_${runId}.py`);
try {
await fs.writeFile(tmpFile, code);
return new Promise(resolve => {
const proc = spawn('python3', [tmpFile], { timeout });
let stdout = '', stderr = '';
proc.stdout.on('data', d => stdout += d.toString());
proc.stderr.on('data', d => stderr += d.toString());
const timer = setTimeout(() => {
proc.kill();
resolve({ success: false, error: 'TIMEOUT', language: 'python', stdout, stderr });
}, timeout);
proc.on('close', async exitCode => {
clearTimeout(timer);
try { await fs.unlink(tmpFile); } catch {}
resolve({ success: exitCode === 0, exitCode, language: 'python', stdout, stderr });
});
proc.on('error', async () => {
clearTimeout(timer);
try { await fs.unlink(tmpFile); } catch {}
resolve({ success: false, exitCode: 1, language: 'python', stdout: '', stderr: 'Python not available on this system. Use Docker for Python execution.' });
});
});
} catch (e) {
return { success: false, error: e.message, language: 'python', stdout: '', stderr: e.message };
}
}
}
export const sandbox = new IsolateSandbox();
await sandbox.init();