"use client"; // Core JavaScript execution engine with security sandbox import { safe } from "ts-safe"; import { CodeRunnerOptions, CodeRunnerResult, LogEntry, } from "./code-runner.interface"; // Security: Block dangerous keywords that could compromise sandbox const FORBIDDEN_KEYWORDS = [ // DOM and browser globals "document.", "globalThis.", "self.", "window", "frames", "opener", // Code execution (but not function declarations) "eval", "constructor", "prototype", "__proto__", // Node.js environment "process.", "require", "exports", // Dangerous objects "Worker", "SharedWorker", "ServiceWorker", "MessageChannel", // Network bypass attempts "XMLHttpRequest", "WebSocket", "EventSource", ]; // Enhanced security check with pattern detection function validateCodeSafety(code: string): string | null { // Check forbidden keywords for (const keyword of FORBIDDEN_KEYWORDS) { const regex = new RegExp(`\\b${keyword}\\b`); if (regex.test(code)) { return `Forbidden keyword: '${keyword}' - not allowed for security reasons`; } } // Detect obvious infinite loop patterns that would block the event loop const infiniteLoopPatterns = [ { pattern: /while\s*\(\s*true\s*\)/, message: "Infinite while loop detected", }, { pattern: /for\s*\(\s*;\s*;\s*\)/, message: "Infinite for loop detected", }, { pattern: /while\s*\(\s*1\s*\)/, message: "Infinite while loop detected", }, { pattern: /for\s*\(\s*;\s*true\s*;\s*\)/, message: "Infinite for loop detected", }, ]; for (const { pattern, message } of infiniteLoopPatterns) { if (pattern.test(code)) { return `Dangerous infinite loop pattern: ${message}`; } } // Detect suspicious patterns that might bypass security const suspiciousPatterns = [ { pattern: /['"`]\s*\+\s*['"`]/g, message: "String concatenation to access globals", }, { pattern: /\[['"`][a-zA-Z_$][a-zA-Z0-9_$]*['"`]\]/g, message: "Dynamic property access", }, { pattern: /eval\s*\(/, message: "Dynamic code evaluation" }, { pattern: /(new\s+)?Function\s*\(/, message: "Function constructor" }, { pattern: /constructor\s*\(/, message: "Constructor access" }, { pattern: /prototype\s*\[/, message: "Prototype manipulation" }, { pattern: /(__proto__|\.constructor)/, message: "Prototype chain access", }, ]; for (const { pattern, message } of suspiciousPatterns) { if (pattern.test(code)) { return `Suspicious pattern detected: ${message}`; } } return null; } // Create a controlled execution environment with safe APIs function createSafeEnvironment( logCapture: (type: LogEntry["type"], ...args: any[]) => void, ) { const safeConsole = { log: (...args: any[]) => logCapture("log", ...args), info: (...args: any[]) => logCapture("info", ...args), warn: (...args: any[]) => logCapture("warn", ...args), error: (...args: any[]) => logCapture("error", ...args), debug: (...args: any[]) => logCapture("debug", ...args), trace: (...args: any[]) => logCapture("trace", ...args), }; // Safe global objects and functions const safeGlobals = { // Console for output console: safeConsole, // Standard JavaScript objects Math: Math, JSON: JSON, Date: Date, Array: Array, Object: Object, String: String, Number: Number, Boolean: Boolean, RegExp: RegExp, Promise: Promise, // Utility functions parseInt: parseInt, parseFloat: parseFloat, isNaN: isNaN, isFinite: isFinite, encodeURIComponent: encodeURIComponent, decodeURIComponent: decodeURIComponent, // Safe browser APIs (if available) ...(typeof self !== "undefined" && { fetch: self.fetch, setTimeout: self.setTimeout, setInterval: self.setInterval, clearTimeout: self.clearTimeout, clearInterval: self.clearInterval, btoa: self.btoa, atob: self.atob, }), // Node.js environment APIs (for testing) ...(typeof global !== "undefined" && typeof self === "undefined" && { setTimeout: global.setTimeout.bind(global), setInterval: global.setInterval.bind(global), clearTimeout: global.clearTimeout.bind(global), clearInterval: global.clearInterval.bind(global), }), }; return { safeGlobals }; } // Wrap code in async function to enable await function wrapCode(code: string): string { return `"use strict";\nreturn (async () => {\n${code}\n})()`; } async function execute({ code, timeout = 5000, onLog, }: CodeRunnerOptions): Promise { const startTime = Date.now(); const logs: LogEntry[] = []; let returnValue: any = undefined; // Capture logs const logCapture = (type: LogEntry["type"], ...args: any[]) => { const entry: LogEntry = { type, args: args.map((v) => ({ type: "data", value: v, })), }; logs.push(entry); if (onLog) onLog(entry); }; // Validate code safety const securityError = validateCodeSafety(code); if (securityError) { return { success: false, error: securityError, logs, executionTimeMs: Date.now() - startTime, }; } // Create safe execution environment const { safeGlobals } = createSafeEnvironment(logCapture); const wrappedCode = wrapCode(code); // Execute with timeout protection try { await Promise.race([ // Code execution new Promise(async (resolve, reject) => { try { const func = new Function(...Object.keys(safeGlobals), wrappedCode); const result = func(...Object.values(safeGlobals)); if (result && typeof result.then === "function") { returnValue = await result; } else { returnValue = result; } resolve(undefined); } catch (error: any) { reject(error); } }), // Timeout new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Execution timeout: ${timeout}ms limit exceeded`)); }, timeout); }), ]); return { success: true, logs, executionTimeMs: Date.now() - startTime, result: returnValue, }; } catch (error: any) { logs.push({ type: "error", args: [{ type: "data", value: error }], }); return { success: false, error: error.message || "Unknown execution error", logs, executionTimeMs: Date.now() - startTime, }; } } export async function safeJsRun({ code, timeout = 5000, onLog, }: CodeRunnerOptions): Promise { return safe(async () => { const result = await execute({ code, timeout, onLog, }); if (!result.success) { throw new Error(result.error || "Code execution failed"); } return { logs: result.logs, executionTimeMs: result.executionTimeMs, result: result.result, success: true, }; }) .ifFail((err) => { return { success: false, error: err.message, logs: [], solution: `JavaScript execution failed. Common issues: • Syntax errors: Check for missing semicolons, brackets, or quotes • Forbidden operations: Avoid DOM access, eval(), or global object manipulation • Infinite loops: Code execution times out after ${timeout}ms • API errors: Check network connectivity for fetch() calls • Type errors: Verify data types and object properties exist • Reference errors: Make sure all variables and functions are defined Available APIs: Math, JSON, Date, fetch, setTimeout, console.log Input data properties are available as variables in your code scope. Use console.log() to output results and debug information.`, }; }) .unwrap(); }