这可能是一个特定的问题(研究项目),但我正在寻找一种使该程序比目前明显“更安全”的好方法。
目标是接受任意用户代码(在通过单元测试和人工检查后)并将其合并到正在运行的程序中,而无需重新加载应用程序。
具体的例子是它是一个持续执行的绘图应用程序,我希望学生/其他用户能够提交一个脚本(遵循特定的格式/指南)。
我目前的实现是要求用户指定他们的类名匹配他们的文件名(例如,
myclass.js
里面有一个const myclass = class { ... }
块)。然后当我看到我的服务器中存在一个新文件时(一个检查特定目录内容的 Flask 应用程序),JavaScript 应用程序将加载该特定文件。
目前我有一个
promise
来确保脚本被加载,但我正在做的一般方法是将它带入内存:
function loadNewScript(scriptName) {
let s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", `/static/techniques/${scriptName}`);
let nodes = document.getElementsByTagName("*");
let node = nodes[nodes.length - 1].parentNode;
node.appendChild(s);
}
但是,我预期的安全问题(除了我想的预期任意代码)是我目前正在使用
eval
将类带入内存:
// technique comes in as 'myclass.js'
function loadObject(technique) {
try {
let obj = eval(technique.split(".")[0]);
let _activeObj = new obj();
if (typeof _activeObj != "undefined") {
return _activeObj;
}
return null;
}
catch (e) { // will check for syntax errors, but this usually trips when the script isn't loaded yet
return null;
}
}
目前这可行,但是我有点担心在这里使用
eval
。我看过有关使用 window
或 this
命名空间的帖子,但据我了解,您不能将动态加载的脚本添加到全局命名空间中吗?无论如何,使用 window['myclass']
或 this['myclass']
在加载后不起作用。 (当我将其名称作为字符串时如何执行 JavaScript 函数)
实际上,如果您已经将“任意”(审查过的)代码加载到动态
eval
中,那么为了安全而避免 <script>
毫无意义。 window['myclass']
不起作用的问题是因为 const
确实声明了全局变量但创建了全局对象的属性。您需要更改脚本以改为使用 var myclass = class { ... };
。
但是,我建议您更改实现以要求文件遵循 ES6 模块格式:
export default class myclass { ... }
然后你可以加载这些使用
function loadNewScript(scriptName) {
return import(`/static/techniques/${scriptName}`);
}
async function loadObject(technique) {
const module = await loadNewScript(technique);
const obj = module.default;
return new obj();
}
如果你需要
loadObject
方法是同步的(而不是返回一个promise),你可以使用对象或者Map
作为类注册:
const registry = {};
async function loadNewScript(scriptName) {
const module = await import(`/static/techniques/${scriptName}`);
registry[scriptName] = module.default;
}
function loadObject(technique) {
const obj = registry[technique];
return obj ? new obj() : null;
}