Electron代码沙箱实战:构建安全的AI代码验证环境,支持JS/Python双语言

本文将介绍如何在 Electron 应用中构建一个安全的代码运行环境,用于验证 AI 生成的 JavaScript 和 Python 代码。

背景与需求

在开发 AI 辅助编程工具时,我们经常需要验证 AI 生成的代码片段是否能正确运行。这要求我们构建一个安全的沙箱环境,让用户能够:

  • 在隔离环境中运行 JavaScript/HTML 代码
  • 执行 Python 代码并查看输出结果
  • 确保主机系统安全,防止恶意代码破坏

技术架构

整体设计

我们使用 Electron 作为基础框架,利用其多窗口能力和主进程-渲染进程隔离特性,构建了一个双模式代码运行器:

  • JavaScript 模式:通过 iframe 沙箱运行 HTML/JS 代码
  • Python 模式:通过 Python 3.11 虚拟环境执行 Python 代码

核心模块

1. 窗口管理模块

javascript 复制代码
// electron/windowManager.js
const { BrowserWindow } = require("electron");
const path = require("path");

function openCodeWin(options) {
  const { initUrl, env, title, type, codeData } = options;  
  
  let win = new BrowserWindow({
    useContentSize: true,
    width: 610,
    height: 450,
    minWidth: 328,
    minHeight: 450,
    show: false,
    title,
    icon: path.join(__dirname, '../dist/logo-html.png'),
    webPreferences: {
      preload: path.join(__dirname, "../../electron/preload.js"),
      webSecurity: true,
      sandbox: false,
      nodeIntegrationInWorker: true,
      contextIsolation: true,
      nodeIntegration: true,
      partition: String(new Date().getTime()) // 隔离会话
    },
  });

  // 窗口配置
  win.setIcon(path.join(__dirname, "../../dist/logo-html.png"));
  win.center();
  win.setMenu(null);

  // 根据代码类型加载不同页面
  if(type === 'html') {
    win.loadURL(initUrl + "#/htmlRunDrwer");
  } else if(type === 'python') {
    win.loadURL(initUrl + "#/pythonRunDrwer");
  }

  // 开发环境下打开开发者工具
  if (env === "development" || env === "testing") {
    win.webContents.openDevTools({ mode: "detach" });
  }

  // 防止标题被更改
  win.on('page-title-updated', (event) => {
    event.preventDefault();
  });

  // 页面加载完成后传递代码数据
  win.webContents.on('did-finish-load', () => {
    win.webContents.executeJavaScript(
      `sessionStorage.setItem('codeData', ${JSON.stringify(codeData)});`
    );
  });

  win.on("closed", () => {
    win.removeAllListeners();
    win = null;
  });
}

module.exports = { openCodeWin };

2. JavaScript 代码运行器

vue 复制代码
<!-- components/HtmlRunDrawer.vue -->
<template>
  <iframe class="html-run-drawer" id="output"></iframe>
</template>

<script setup>
import { ref, nextTick, onMounted, onUnmounted } from "vue";
import { ElMessage } from "element-plus";

const codeData = ref("");
const loading = ref(false);
let loadingTimeout = null;
let isRunning = false;

// 从 sessionStorage 获取代码数据
const readSessionTimer = ref(null)
const getCode = () => {
  const code = sessionStorage.getItem("codeData");
  if(!code){
    readSessionTimer.value = setTimeout(() => {
      getCode()
    }, 100);
  }else {
    handleRunButtonClick(code);
    clearTimeout(readSessionTimer.value);
  }
}

onMounted(() => {
  getCode()
});

onUnmounted(() => {
    loading.value = false;
    isRunning = false;
    clearTimeout(loadingTimeout);
})

const handleRunButtonClick = (code) => {
    if (isRunning) return;
    codeData.value = code;
    OpenDrawer();
};

const OpenDrawer = () => {
    if (isRunning) return;
    isRunning = true;
    loading.value = true;

    // 设置超时保护
    loadingTimeout = setTimeout(() => {
        if (loading.value) {
            ElMessage.warning("加载超时,请重新尝试");
            loading.value = false;
            isRunning = false;
        }
    }, 30000); // 30秒超时

    nextTick(() => {
        const iframe = document.getElementById("output");
        if (iframe) {
            // 使用 srcdoc 在沙箱中运行代码
            iframe.srcdoc = codeData.value;
            iframe.onload = () => {
                clearTimeout(loadingTimeout);
                loading.value = false;
                isRunning = false;
            };
        } else {
            console.error("iframe 元素未找到");
            loading.value = false;
            isRunning = false;
            clearTimeout(loadingTimeout);
        }
    });
};
</script>

<style scoped>
.html-run-drawer{
  width: 100vw;
  height: 100vh;
  border: none;
}
</style>

3. Python 代码运行器

vue 复制代码
<!-- components/PythonRunDrawer.vue -->
<template>
    <div class="python-run-drwer">
        <div v-if="loading" class="loading-overlay">
            <p class="loading_Text"></p>
        </div>
        <div v-if="ShowOutput" style="margin-bottom: 5px;margin-left: 5px;">输出:</div>
        <div v-if="ShowErrorOutput" style="margin-bottom: 5px;margin-left: 5px;">报错:</div>
        <div class="codeEditor_head">
            <div v-if="ShowOutput">
                <div class="output">{{ output }}</div>
            </div>
            <div v-if="ShowErrorOutput">
                <div class="output">{{ errorOutput }}</div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, nextTick, onMounted, onUnmounted } from "vue";

const codeData = ref("");
const output = ref("");
const errorOutput = ref("");
const ShowErrorOutput = ref(false);
const ShowOutput = ref(false);
const loading = ref(false);

onMounted(() => {
    getCode()
});

const readSessionTimer = ref(null)
const getCode = () => {
  const code = sessionStorage.getItem("codeData");
  if(!code){
    readSessionTimer.value = setTimeout(() => {
      getCode()
    }, 100);
  }else {
    handleRunButtonClick(code);
    clearTimeout(readSessionTimer.value);
  }
}

const handleRunButtonClick = (code) => {
    codeData.value = code;
    OpenPythonDrawer();
};

const OpenPythonDrawer = async () => {
    loading.value = true;
    nextTick(async () => {
        try {
            // 添加 UTF-8 编码支持
            const fullCode = `import sys\nsys.stdout.reconfigure(encoding='utf-8')\n${codeData.value}`;
            output.value = await window.electronAPI.runPython(fullCode);
            ShowOutput.value = true;
        } catch (error) {
            ShowErrorOutput.value = true;
            errorOutput.value = error;
        } finally {
            loading.value = false;
        }
    })
};
</script>

4. Python 执行器(主进程)

javascript 复制代码
// electron/pythonRunner.js
const { ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');
const { exec } = require('child_process');

function setupPythonRunner() {
  ipcMain.handle("run-python", async (event, code) => {
    return new Promise((resolve, reject) => {
      const exePath = path.join(outputDir, 'python.exe');
      const tempFilePath = path.join(outputDir, 'temp_script.py');
      
      // 创建临时文件
      fs.writeFileSync(tempFilePath, code, "utf-8");
      
      // 执行 Python 代码
      exec(`"${exePath}" "${tempFilePath}"`, { 
        encoding: 'utf-8',
        timeout: 30000 // 30秒超时
      }, (error, stdout, stderr) => {
        // 清理临时文件
        try {
          fs.unlinkSync(tempFilePath);
        } catch (e) {
          console.warn('临时文件清理失败:', e);
        }
        
        if (error) {
          reject(stderr || error.message);
        } else {
          resolve(stdout);
        }
      });
    });
  });
}

module.exports = { setupPythonRunner };

安全考虑与最佳实践

1. 环境隔离

  • 会话隔离 :使用 partition 为每个窗口创建独立的会话
  • 进程隔离:利用 Electron 的进程模型,将代码执行与主应用隔离
  • 文件系统隔离:Python 代码在临时文件中执行,执行后立即清理

2. 资源限制

  • 超时控制:设置 30 秒执行超时,防止无限循环
  • 内存限制:通过 iframe 沙箱限制 JavaScript 代码的资源访问
  • 错误处理:完善的异常捕获和用户反馈

3. 用户体验优化

  • 加载状态:提供清晰的加载指示器
  • 错误展示:区分正常输出和错误信息
  • 响应式设计:适配不同尺寸的代码运行结果

使用示例

运行 HTML/JavaScript 代码

示例

javascript 复制代码
// 在渲染进程中调用
const code = `
<!DOCTYPE html>
<html>
<head>
    <title>测试页面</title>
</head>
<body>
    <h1>Hello World!</h1>
    <script>
        console.log('JavaScript 代码正常运行');
        document.body.innerHTML += '<p>动态添加的内容</p>';
    </script>
</body>
</html>
`;

window.electronAPI.openCodeWindow({
    type: 'html',
    codeData: code,
    title: 'HTML 代码运行结果'
});

实际使用

AI回复 复制代码
下面是一个使用 **纯 JavaScript + HTML5 Canvas** 编写的 **五子棋(Gomoku)** 游戏,支持双人对战、胜负判断、重新开始等功能,无需任何外部库。

---

### ✅ 特点:
- 黑白轮流下子(黑先)
- 自动检测五子连珠(横、竖、正斜、反斜)
- 胜利提示
- 点击"重新开始"按钮重置棋盘
- 响应式 Canvas 绘制

---

### 📄 完整代码(保存为 `gomoku.html`,直接用浏览器打开即可):

```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>五子棋 - Gomoku</title>
  <style>
    body {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      margin: 0;
      background-color: #f5f5dc;
      font-family: "Microsoft YaHei", sans-serif;
    }
...

运行效果

运行 Python 代码

示例

javascript 复制代码
// 在渲染进程中调用
const pythonCode = `
for i in range(5):
    print(f"当前数字: {i}")

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

print(f"5 的阶乘是: {factorial(5)}")
`;

window.electronAPI.openCodeWindow({
    type: 'python', 
    codeData: pythonCode,
    title: 'Python 代码运行结果'
});

实际使用

AI回复 复制代码
下面是一个使用 Python 和 Pygame 编写的简单俄罗斯方块游戏。这个游戏包含了基本的俄罗斯方块功能:方块下落、左右移动、旋转、消除行、计分等。

---

### ✅ 环境要求:
- Python 3.x
- 安装 Pygame:`pip install pygame`

---

### 🧩 代码如下(保存为 `tetris.py`):

```python
import pygame
import random
import sys

# 初始化 pygame
pygame.init()

# 常量
SCREEN_WIDTH = 300
SCREEN_HEIGHT = 600
GRID_SIZE = 30
GRID_WIDTH = SCREEN_WIDTH // GRID_SIZE
GRID_HEIGHT = SCREEN_HEIGHT // GRID_SIZE

# 颜色定义
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)

# 方块形状(7种经典俄罗斯方块)
SHAPES = [
    [[1, 1, 1, 1]],  # I
    [[1, 1, 1], [0, 1, 0]],  # T
    [[1, 1, 1], [1, 0, 0]],  # L
    [[1, 1, 1], [0, 0, 1]],  # J
    [[1, 1], [1, 1]],  # O
    [[0, 1, 1], [1, 1, 0]],  # S
    [[1, 1, 0], [0, 1, 1]]   # Z
]

# 对应颜色
COLORS = [CYAN, MAGENTA, ORANGE, BLUE, YELLOW, GREEN, RED]

# 创建窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("俄罗斯方块 - Tetris")
...

运行效果

总结

通过 Electron 构建的代码运行沙箱提供了以下优势:

  1. 安全性:在隔离环境中执行不可信代码
  2. 灵活性:支持多种编程语言
  3. 用户体验:实时反馈代码执行结果
  4. 稳定性:完善的错误处理和资源管理

这种架构特别适合需要验证 AI 生成代码的场景,为开发者提供了一个安全、可靠的代码验证环境。在实际项目中,还可以进一步扩展支持更多语言,增加代码分析功能,提升工具的实用性。

相关推荐
加洛斯3 小时前
AJAX 知识篇(2):Axios的核心配置
前端·javascript·ajax
Cache技术分享3 小时前
207. Java 异常 - 访问堆栈跟踪信息
前端·后端
Mintopia3 小时前
开源数据集在 WebAI 模型训练中的技术价值与风险:当我们把互联网塞进显存
前端·javascript·aigc
写不来代码的草莓熊3 小时前
vue前端面试题——记录一次面试当中遇到的题(3)
前端·javascript·vue.js
道可到3 小时前
写了这么多代码,你真的在进步吗??—一个前端人的反思与全栈突围路线
前端
小虎AI生活3 小时前
CodeBuddy实战:小虎个人博客网站,AI编程就是升级打boss的过程
人工智能·ai编程·codebuddy
马腾化云东4 小时前
FastJsMcp:几行代码开发一个mcp工具
人工智能·ai编程·mcp
凡大来啦4 小时前
v-for渲染的元素上使用ref
前端·javascript·vue.js
道可到4 小时前
前端开发的生存法则:如何从“像素工人”进化为价值创造者?
前端