安全用户编程语言相关#
Data: 2026-04-08 00:19:24
我完全明白你的需求!#
你的核心问题是:
想让用户用代码程序化生成方块建筑,但又不能让用户的代码访问到全局变量
k(引擎核心),需要一个安全的沙盒执行环境。
方案对比#
| 方案 | 体积 | 安全性 | 易用性 | 推荐度 |
|---|---|---|---|---|
| 原生 JS 沙盒 | 0kb | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Lua (Fengari) | ~150kb | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| QuickJS-WASM | ~300kb+ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| 自创 DSL | 0kb | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
⭐ 最推荐:原生 JS 沙盒(0kb 额外体积!)#
既然你对体积极度敏感(40kb 引擎),最好的方案是 根本不引入额外库,用浏览器自带的能力做沙盒:
核心思路:Function 构造器 + 白名单注入#
function runUserCode(userCode) {
// 用户代码只能通过 addBlock 来放方块,返回方块数组
const blocks = [];
// 只暴露你允许的 API
const api = {
addBlock: (x, y, z, type) => {
// 限制坐标范围,防止恶意超大数据
if (Math.abs(x) > 1000 || Math.abs(y) > 1000 || Math.abs(z) > 1000) return;
if (blocks.length > 50000) return; // 限制方块总数
blocks.push({ x, y, z, type: type || 1 });
},
// 可以给一些数学工具
sin: Math.sin,
cos: Math.cos,
sqrt: Math.sqrt,
floor: Math.floor,
random: Math.random,
PI: Math.PI,
};
// 🔑 关键:构造一个与全局完全隔离的函数
// 参数名就是用户能访问的全部变量
const paramNames = Object.keys(api);
const paramValues = Object.values(api);
try {
// 创建函数,用户代码在这个函数体内执行
const sandbox = new Function(
...paramNames,
// 🛡️ 严格模式,禁止 this 泄漏到 window
`"use strict";\n${userCode}`
);
// 调用时,this 设为 null,只传入白名单变量
sandbox.call(null, ...paramValues);
} catch (e) {
console.error('用户代码出错:', e.message);
}
return blocks;
}用户写代码的体验(非常友好):#
// 用户代码示例:生成一个圆柱体
for (let y = 0; y < 20; y++) {
for (let a = 0; a < PI * 2; a += 0.1) {
let r = 5;
addBlock(floor(cos(a) * r), y, floor(sin(a) * r), 2);
}
}// 用户代码示例:生成一个球体
let R = 10;
for (let x = -R; x <= R; x++) {
for (let y = -R; y <= R; y++) {
for (let z = -R; z <= R; z++) {
if (sqrt(x*x + y*y + z*z) <= R) {
addBlock(x, y + R, z, 1);
}
}
}
}🛡️ 再加一层安全防护#
function runUserCodeSafe(userCode) {
// 1️⃣ 静态检查:禁止危险关键词
const banned = [
'window', 'document', 'globalThis', 'self',
'eval', 'Function', 'import', 'require',
'fetch', 'XMLHttpRequest', 'WebSocket',
'localStorage', 'sessionStorage', 'cookie',
'setTimeout', 'setInterval', 'requestAnimationFrame',
'__proto__', 'prototype', 'constructor',
];
// 你的全局变量名!
banned.push('k');
for (const word of banned) {
// 用正则检测,避免字符串中的误判可以更精细
// 但对于安全场景,宁可误杀
if (userCode.includes(word)) {
throw new Error(`禁止使用: "${word}"`);
}
}
// 2️⃣ 限制代码长度
if (userCode.length > 10000) {
throw new Error('代码太长了');
}
// 3️⃣ 执行(带超时保护)
const blocks = [];
let count = 0;
const MAX_OPS = 1000000; // 防止死循环
const api = {
addBlock: (x, y, z, type) => {
if (++count > MAX_OPS) throw new Error('操作次数超限');
if (blocks.length > 50000) return;
if (Math.abs(x) > 500 || Math.abs(y) > 500 || Math.abs(z) > 500) return;
blocks.push({
x: Math.floor(x),
y: Math.floor(y),
z: Math.floor(z),
type: (type | 0) || 1
});
},
sin: Math.sin, cos: Math.cos, tan: Math.tan,
sqrt: Math.sqrt, abs: Math.abs, pow: Math.pow,
floor: Math.floor, ceil: Math.ceil, round: Math.round,
random: Math.random, PI: Math.PI, min: Math.min, max: Math.max,
};
const paramNames = Object.keys(api);
const paramValues = Object.values(api);
try {
const fn = new Function(...paramNames, `"use strict";\n${userCode}`);
fn.call(null, ...paramValues);
} catch (e) {
console.warn('沙盒执行错误:', e.message);
}
return blocks;
}为什么这个方案最适合你?#
- 0 字节额外体积 —— 纯浏览器原生能力
- 用户写的就是 JS —— 学习成本为零,
for循环、变量、函数都能用 k完全不可见 —— 在new Function里,用户代码的作用域链上根本没有你的全局变量("use strict"模式下this也是undefined)- 白名单机制 —— 用户只能用你给的
addBlock、sin、cos等
Web Worker 方案(依然 0 体积)#
把用户代码丢到 Web Worker 里执行,Worker 天然没有 DOM 访问权限,也完全看不到主线程的任何变量:
function runInWorker(userCode) {
return new Promise((resolve, reject) => {
const workerCode = `
const blocks = [];
const api = {
addBlock(x,y,z,t) {
if(blocks.length<50000) blocks.push({x:x|0,y:y|0,z:z|0,type:t||1});
},
sin:Math.sin, cos:Math.cos, sqrt:Math.sqrt,
floor:Math.floor, PI:Math.PI, random:Math.random,
};
with(api) {
try { ${userCode} } catch(e) {}
}
postMessage(blocks);
`;
const blob = new Blob([workerCode], { type: 'text/javascript' });
const w = new Worker(URL.createObjectURL(blob));
// 超时 5 秒自动杀掉(防死循环)
const timer = setTimeout(() => {
w.terminate();
reject(new Error('执行超时'));
}, 5000);
w.onmessage = (e) => {
clearTimeout(timer);
w.terminate();
resolve(e.data); // 拿到 blocks 数组
};
w.onerror = (e) => {
clearTimeout(timer);
w.terminate();
reject(e);
};
});
}
// 使用
const blocks = await runInWorker(userCode);
blocks.forEach(b => k.addBlockToScene(b.x, b.y, b.z, b.type));Worker 方案的优势:
- ✅ 真正的线程隔离,用户代码 100% 无法访问
k、window、document - ✅ 天然支持超时终止(
worker.terminate()) - ✅ 死循环也不会卡住主页面
- ✅ 依然是 0 字节额外体积
总结#
不需要 Lua,不需要任何外部库。 用
new Function+ 关键词过滤就够了。如果想要终极安全,用 Web Worker。两个方案都是 0 额外体积,完美匹配你 40kb 引擎的极简哲学。