How to restrict global scope in dynamically created function?

I want to dynamically create a function using Function() constructor. How can I restrict access to global scope? For example, this code:
console.log(new Function('return Deno.exit(1)')())
console.log(new Function('return Deno.exit(1)')())
should return something like Uncaught ReferenceError: Deno is not defined instead of providing full access to Deno's properties and methods. Is it even possible?
9 Replies
lcasdev
lcasdev•6mo ago
This is not possible
marvinh.
marvinh.•6mo ago
yup like luca said, it is not possible
Pavel (Pasha) Lechenko
Got it. Would be nice to see it in the roadmap 😉 A simple scenario/use-case - I want to allow my users to evaluate their ts/js script snippets in context of my object(s). In nodejs there is another option to run some code in restricted sandbox - use vm (https://nodejs.org/api/vm.html). In Deno I get:
~$ deno
Deno 1.40.3
exit using ctrl+d, ctrl+c, or close()
REPL is running with all permissions allowed.
To specify permissions, run `deno repl` with allow flags.
> import vm from 'node:vm'
undefined
> vm.runInNewContext('a',{a:123})
Uncaught ReferenceError: a is not defined
at <anonymous>:1:1
> const ctx = vm.createContext({a:123})
Uncaught Error: Not implemented: createContext
at notImplemented (ext:deno_node/_utils.ts:9:9)
at Object.createContext (node:vm:33:3)
at <anonymous>:1:37
>
~$ deno
Deno 1.40.3
exit using ctrl+d, ctrl+c, or close()
REPL is running with all permissions allowed.
To specify permissions, run `deno repl` with allow flags.
> import vm from 'node:vm'
undefined
> vm.runInNewContext('a',{a:123})
Uncaught ReferenceError: a is not defined
at <anonymous>:1:1
> const ctx = vm.createContext({a:123})
Uncaught Error: Not implemented: createContext
at notImplemented (ext:deno_node/_utils.ts:9:9)
at Object.createContext (node:vm:33:3)
at <anonymous>:1:37
>
Are there any plans to implement/support this?
marvinh.
marvinh.•6mo ago
Quoting node's documentation page about the vm modules https://nodejs.org/api/vm.html
The node:vm module is not a security mechanism. Do not use it to run untrusted code.
No description
marvinh.
marvinh.•6mo ago
FYI: Wasn't on our radar so far that there is a bug with the second parameter. I've filed an issue https://github.com/denoland/deno/issues/22395
GitHub
Bug: Second argument to runInNewContext not supported · Issue #22...
Input code: import * as vm from "node:vm"; vm.runInNewContext('a',{a:123}) Node: $ node Welcome to Node.js v20.11.0. Type ".help" for more information. > vm.runInNewC...
ioB
ioB•6mo ago
This is technically possible but I would strongly, strongly recommend against it If you use the deprecated with statement you can replace globalThis with a proxy which allows you to define what code inside the with statement has access to
Pavel (Pasha) Lechenko
Thank you. Sounds like dirty workaround. @lino-levan Here is a dirty trick:
console.log(
new Function(
[...Object.getOwnPropertyNames(globalThis)],
`const z = [];
for(const k of this.b) z.push(-k);
return [
self,
globalThis,
Deno,
crypto,
Object,
String,
Array,
this,
this.a.toUpperCase(),
this.b.filter(v => v > 2),
z]`
).bind(
{ a : "QWErty",
b : [ 1, 2, 3, 4, 3, 2, 1 ]
})
.call()
)
console.log(
new Function(
[...Object.getOwnPropertyNames(globalThis)],
`const z = [];
for(const k of this.b) z.push(-k);
return [
self,
globalThis,
Deno,
crypto,
Object,
String,
Array,
this,
this.a.toUpperCase(),
this.b.filter(v => v > 2),
z]`
).bind(
{ a : "QWErty",
b : [ 1, 2, 3, 4, 3, 2, 1 ]
})
.call()
)
This undefines all members of globalThis and allows access only to this poor-man's expression evaluator 🤣
ioB
ioB•6mo ago
interesting
Pavel (Pasha) Lechenko
even cooler:
const xxx = new Function(
'$',
[...Object.getOwnPropertyNames(globalThis)],
`const z = [];
for(const k of this.b) z.push(-k);
$.log('\\n\\nHello Console!\\n\\n');
return {
self: self,
globalThis: globalThis,
Deno: Deno,
crypto: crypto,
Object: Object,
String: String,
Array: Array,
this: this,
'this.a.toUpperCase()': this.a.toUpperCase(),
'this.b.filter(v => v > 2)': this.b.filter(v => v > 2),
'z': z}`
).bind(
{ a : "QWErty",
b : [ 1, 2, 3, 4, 3, 2, 1 ]
})
({log:console.log});

console.log('xxx', xxx)
const xxx = new Function(
'$',
[...Object.getOwnPropertyNames(globalThis)],
`const z = [];
for(const k of this.b) z.push(-k);
$.log('\\n\\nHello Console!\\n\\n');
return {
self: self,
globalThis: globalThis,
Deno: Deno,
crypto: crypto,
Object: Object,
String: String,
Array: Array,
this: this,
'this.a.toUpperCase()': this.a.toUpperCase(),
'this.b.filter(v => v > 2)': this.b.filter(v => v > 2),
'z': z}`
).bind(
{ a : "QWErty",
b : [ 1, 2, 3, 4, 3, 2, 1 ]
})
({log:console.log});

console.log('xxx', xxx)
This returns:
Hello Console!


xxx {
self: undefined,
globalThis: undefined,
Deno: undefined,
crypto: undefined,
Object: undefined,
String: undefined,
Array: undefined,
this: {
a: "QWErty",
b: [
1, 2, 3, 4,
3, 2, 1
]
},
"this.a.toUpperCase()": "QWERTY",
"this.b.filter(v => v > 2)": [ 3, 4, 3 ],
z: [
-1, -2, -3, -4,
-3, -2, -1
]
}
Hello Console!


xxx {
self: undefined,
globalThis: undefined,
Deno: undefined,
crypto: undefined,
Object: undefined,
String: undefined,
Array: undefined,
this: {
a: "QWErty",
b: [
1, 2, 3, 4,
3, 2, 1
]
},
"this.a.toUpperCase()": "QWERTY",
"this.b.filter(v => v > 2)": [ 3, 4, 3 ],
z: [
-1, -2, -3, -4,
-3, -2, -1
]
}