class Sandbox {
constructor() {
if (!(this instanceof Sandbox))
return new Sandbox()
const iframe = this.iframe = document.createElement('iframe')
document.body.appendChild(iframe)
const SandboxedFunction = iframe.contentWindow.constructor.constructor,
[
SandboxedAsyncGeneratorFunction,
SandboxedAsyncFunction,
SandboxedGeneratorFunction,
] = new SandboxedFunction(`return [
(async function*() {}).constructor,
(async function () {}).constructor,
( function*() {}).constructor,
]`)()
document.body.removeChild(iframe)
this.SandboxedFunction = SandboxedFunction
this.SandboxedAsyncGeneratorFunction = SandboxedAsyncGeneratorFunction
this.SandboxedAsyncFunction = SandboxedAsyncFunction
this.SandboxedGeneratorFunction = SandboxedGeneratorFunction
this.wrap = new SandboxedFunction(`
return function wrap(value) {
switch (typeof value) {
case 'bigint':
case 'boolean':
case 'number':
case 'string':
case 'symbol':
case 'undefined':
// Primitives are copied by the JS runtime, and therefore cannot "leak"
return value
case 'function':
// We return a wrapper around the function, which means that accessing
// 'v.prototype.constructor' will return a sandboxed Function, rather
// than an unsafe Function.
return (...args) => wrap(v.apply(this, args))
case 'object':
// We return another safe wrapper around the object
return new Proxy(value, {
get(target, p) {
return wrap(target[p])
}
})
}
}
`)()
Object.freeze(this)
}
run(code, context, SandboxedFunction = this.SandboxedFunction) {
if (typeof code !== 'string')
throw new Error('Code must be a string.')
if (context !== undefined && (context === null || typeof context !== 'object'))
throw new Error('Context must be an object.')
if (SandboxedFunction !== this.SandboxedFunction &&
SandboxedFunction !== this.SandboxedAsyncGeneratorFunction &&
SandboxedFunction !== this.SandboxedAsyncFunction &&
SandboxedFunction !== this.SandboxedGeneratorFunction)
throw new Error('Invalid sandboxed Function constructor provided.')
const argumentNames = [],
argumentValues = []
for (const key in context || {}) {
argumentNames.push(key)
argumentValues.push(context[key])
}
return new SandboxedFunction(...argumentNames, code)(...argumentValues)
}
runAsync(code, context) {
return this.run(code, context, this.SandboxedAsyncFunction)
}
runGenerator(code, context) {
return this.run(code, context, this.SandboxedGeneratorFunction)
}
runAsyncGenerator(code, context) {
return this.run(code, context, this.SandboxedAsyncGeneratorFunction)
}
}