@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
292 lines (291 loc) • 7.99 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { spawn } from "child_process";
import { promises as fs } from "fs";
import { join } from "path";
import { tmpdir } from "os";
import { randomBytes } from "crypto";
const MAX_OUTPUT_SIZE = 5e4;
const EXECUTION_TIMEOUT = 3e4;
class CodeExecutionHandler {
allowedLanguages = ["python", "javascript", "typescript"];
sandboxDir;
constructor() {
this.sandboxDir = join(tmpdir(), "stackmemory-sandbox");
this.ensureSandboxDir();
}
async ensureSandboxDir() {
try {
await fs.mkdir(this.sandboxDir, { recursive: true });
} catch (error) {
console.error("Failed to create sandbox directory:", error);
}
}
/**
* Execute code in a controlled environment
*/
async executeCode(params) {
const {
language,
code,
workingDirectory,
timeout = EXECUTION_TIMEOUT
} = params;
if (!this.allowedLanguages.includes(language.toLowerCase())) {
return {
success: false,
stdout: "",
stderr: `Language '${language}' is not allowed. Use: ${this.allowedLanguages.join(", ")}`,
exitCode: 1,
truncated: false
};
}
const tempFile = join(
this.sandboxDir,
`code_${randomBytes(8).toString("hex")}.${this.getFileExtension(language)}`
);
try {
await fs.writeFile(tempFile, code, "utf-8");
const result = await this.runCode(
language,
tempFile,
workingDirectory || this.sandboxDir,
timeout
);
await fs.unlink(tempFile).catch(() => {
});
return result;
} catch (error) {
return {
success: false,
stdout: "",
stderr: `Execution error: ${error instanceof Error ? error.message : String(error)}`,
exitCode: 1,
truncated: false
};
}
}
/**
* Run code with appropriate interpreter
*/
async runCode(language, filePath, workingDirectory, timeout) {
const command = this.getCommand(language);
const args = this.getArgs(language, filePath);
return new Promise((resolve) => {
let stdout = "";
let stderr = "";
let truncated = false;
const child = spawn(command, args, {
cwd: workingDirectory,
env: {
...process.env,
PYTHONDONTWRITEBYTECODE: "1",
// Don't create .pyc files
NODE_ENV: "sandbox"
}
});
const timeoutId = setTimeout(() => {
child.kill("SIGTERM");
stderr += "\n[Process killed due to timeout]";
}, timeout);
child.stdout.on("data", (data) => {
stdout += data.toString();
if (stdout.length > MAX_OUTPUT_SIZE) {
truncated = true;
child.kill("SIGTERM");
}
});
child.stderr.on("data", (data) => {
stderr += data.toString();
if (stderr.length > MAX_OUTPUT_SIZE) {
truncated = true;
child.kill("SIGTERM");
}
});
child.on("close", async (code) => {
clearTimeout(timeoutId);
let outputFile;
if (truncated) {
outputFile = join(
this.sandboxDir,
`output_${randomBytes(8).toString("hex")}.txt`
);
await fs.writeFile(
outputFile,
stdout + "\n---STDERR---\n" + stderr,
"utf-8"
).catch(() => {
});
}
resolve({
success: code === 0,
stdout: truncated ? stdout.slice(0, MAX_OUTPUT_SIZE) + "\n[Output truncated]" : stdout,
stderr: truncated ? stderr.slice(0, MAX_OUTPUT_SIZE) + "\n[Output truncated]" : stderr,
exitCode: code,
truncated,
outputFile
});
});
child.on("error", (error) => {
clearTimeout(timeoutId);
resolve({
success: false,
stdout: "",
stderr: `Failed to start process: ${error.message}`,
exitCode: null,
truncated: false
});
});
});
}
/**
* Get file extension for language
*/
getFileExtension(language) {
switch (language.toLowerCase()) {
case "python":
return "py";
case "javascript":
return "js";
case "typescript":
return "ts";
default:
return "txt";
}
}
/**
* Get command for language
*/
getCommand(language) {
switch (language.toLowerCase()) {
case "python":
return "python3";
case "javascript":
return "node";
case "typescript":
return "npx";
default:
return "echo";
}
}
/**
* Get arguments for language
*/
getArgs(language, filePath) {
switch (language.toLowerCase()) {
case "python":
return [filePath];
case "javascript":
return [filePath];
case "typescript":
return ["tsx", filePath];
default:
return ["Unsupported language"];
}
}
/**
* Validate code for dangerous patterns
*/
validateCode(code) {
const warnings = [];
const dangerousPatterns = [
{ pattern: /import\s+os/i, message: "Importing os module detected" },
{
pattern: /import\s+subprocess/i,
message: "Subprocess module detected"
},
{ pattern: /exec\s*\(/i, message: "exec() function detected" },
{ pattern: /eval\s*\(/i, message: "eval() function detected" },
{ pattern: /__import__/i, message: "__import__ detected" },
{
pattern: /open\s*\([^)]*['"]\//i,
message: "Absolute path file access detected"
},
{
pattern: /require\s*\([^)]*child_process/i,
message: "child_process module detected"
},
{ pattern: /require\s*\([^)]*fs/i, message: "fs module access detected" }
];
for (const { pattern, message } of dangerousPatterns) {
if (pattern.test(code)) {
warnings.push(message);
}
}
return {
safe: warnings.length === 0,
warnings
};
}
/**
* Get sandbox status
*/
async getSandboxStatus() {
try {
const files = await fs.readdir(this.sandboxDir);
let totalSize = 0;
for (const file of files) {
const stat = await fs.stat(join(this.sandboxDir, file));
totalSize += stat.size;
}
return {
sandboxDir: this.sandboxDir,
tempFiles: files.length,
totalSize
};
} catch {
return {
sandboxDir: this.sandboxDir,
tempFiles: 0,
totalSize: 0
};
}
}
/**
* Clean sandbox directory
*/
async cleanSandbox() {
try {
const files = await fs.readdir(this.sandboxDir);
for (const file of files) {
await fs.unlink(join(this.sandboxDir, file)).catch(() => {
});
}
} catch (error) {
console.error("Failed to clean sandbox:", error);
}
}
}
const codeExecutionHandlers = {
"code.execute": async (params) => {
const handler = new CodeExecutionHandler();
const validation = handler.validateCode(params.code);
if (!validation.safe && !params.force) {
return {
error: "Code validation failed",
warnings: validation.warnings,
hint: "Add force: true to execute anyway"
};
}
return await handler.executeCode(params);
},
"code.validate": async (params) => {
const handler = new CodeExecutionHandler();
return handler.validateCode(params.code);
},
"code.sandbox_status": async () => {
const handler = new CodeExecutionHandler();
return await handler.getSandboxStatus();
},
"code.clean_sandbox": async () => {
const handler = new CodeExecutionHandler();
await handler.cleanSandbox();
return { success: true, message: "Sandbox cleaned" };
}
};
export {
CodeExecutionHandler,
codeExecutionHandlers
};