Code generation from strings
Make eval(), new Function() and every other way a string becomes code throw, for the whole process, with --disallow-code-generation-from-strings.
--disallow-code-generation-from-strings stops a program from turning strings into code. The flag has two levels:
| Flag | What it refuses |
|---|---|
--disallow-code-generation-from-strings | eval() and the Function constructors. The same as Node.js's flag. |
--disallow-code-generation-from-strings=strict | Every way a string becomes code in the process. Bun only. |
bun --disallow-code-generation-from-strings=strict server.tsnew Function("return 1 + 1"); // EvalError: Code generation from strings disallowed for this contextA refusal is a synchronous EvalError that you can catch. import() of a refused URL rejects with the same error. A library that probes with try { new Function("") } catch {} takes its fallback path.
Two refusals reach you another way. ShadowRealm.prototype.importValue() of a refused URL rejects with a TypeError whose message contains the EvalError. A refused URL in a Worker's preload option is reported by the Worker's error event.
The flag applies to the whole process#
Bun reads the flag at startup. Nothing turns it off afterwards:
- There is no API that re-enables code generation.
- Every
Workerhas the level of the process, whateverexecArgvyou give theWorker. - Every realm and context the program creates later has it:
ShadowRealm,node:vmcontexts under=strict, and everyBun.ModuleGraph. - The level a compiled executable is built with is a floor.
BUN_OPTIONScan raise it and cannot lower it.
When the flag appears more than once on a command line, the last one counts, as for any option. Bun reads BUN_OPTIONS before the command line, so the command line wins.
A Worker whose own execArgv contains the flag throws ERR_WORKER_INVALID_EXEC_ARGV, as in Node.js, whether or not the process has the flag. Pass the flag to the process. new Worker(file, { execArgv: process.execArgv }) throws in a process that has the flag, so leave the flag out of the list you pass on.
Workers whose code is a string#
new Worker(code, { eval: true }) from node:worker_threads takes the Worker's source as a string. So does a Worker started from a data: or blob: URL.
- With the flag and no value, the
Workerstarts, as in Node.js.eval()andnew Function()throw inside it. - With
=strict, theWorkerconstructor throws theEvalError. Put the Worker's code in a file and pass the file's path or URL.
What each level refuses#
| A string becomes code through | Flag | =strict |
|---|---|---|
eval(), direct and indirect | refused | refused |
new Function(), and the async, generator and async generator function constructors, reached any way | refused | refused |
ShadowRealm.prototype.evaluate(), and eval() inside a ShadowRealm | refused | refused |
new vm.Script(), vm.runInThisContext(), vm.runInContext(), vm.runInNewContext() | allowed | refused |
vm.compileFunction(), new vm.SourceTextModule() | allowed | refused |
eval() inside a node:vm context, including one made with codeGeneration: { strings: true } | allowed | refused |
module._compile(), and a require.extensions handler that calls it | allowed | refused |
Assigning a different Module.wrapper | allowed | refused |
import, import() and require() of a data: or blob: URL | allowed | refused |
A Worker started from a data: or blob: URL, or with eval: true | allowed | refused |
A plugin that returns contents for the js, jsx, ts or tsx loader | allowed | refused |
inspector.open() from node:inspector, startRemoteDebugger() from bun:jsc | allowed | refused |
napi_run_script() in a native addon | allowed | refused |
With the flag and no value, Bun matches Node.js: node:vm is not affected, and a node:vm context decides for itself with its codeGeneration option.
These keep working at both levels:
- Importing module files, with literal or computed specifiers, and modules embedded in a compiled executable.
new Worker()with a file.require()andcreateRequire()of files and built-in modules.Bun.ModuleGraph.JSON.parse(),RegExp, andeval()of a value that is not a string.- WebAssembly.
- Native addons and
bun:ffi. See What the flag does not cover. - A plugin that returns an
exportsobject, orcontentsfor a data loader such asjsonortoml. vm.createContext()andnew vm.SyntheticModule(). Neither one compiles a string.
The inspector#
The debugger evaluates the code its client sends, so =strict refuses it. Bun exits with an error at startup when you also pass --inspect, --inspect-wait or --inspect-brk, or when BUN_INSPECT is set:
error: BUN_INSPECT cannot be used with --disallow-code-generation-from-strings=strict: the inspector evaluates code from stringsEditors set BUN_INSPECT_CONNECT_TO for every process started from their terminals. Bun's VS Code extension does so by default. That variable does not mean that you asked to debug this program, so Bun prints a warning, does not connect, and runs the program:
warn: BUN_INSPECT_CONNECT_TO is ignored with --disallow-code-generation-from-strings=strict: the inspector evaluates code from stringsEvery way to reach a debugger, and what =strict does with it:
| Route | With =strict |
|---|---|
--inspect, --inspect-wait, --inspect-brk, BUN_INSPECT | Bun exits with an error at startup |
BUN_INSPECT_CONNECT_TO | Bun prints a warning and connects to nothing |
inspector.open() from node:inspector | throws |
startRemoteDebugger() from bun:jsc | throws |
new inspector.Session() from node:inspector, which needs no server | evaluates nothing, with or without the flag |
$vm, JavaScriptCore's debugging global (BUN_JSC_useDollarVM=1) | not defined |
An in-process Session does not implement Runtime.evaluate, Runtime.compileScript, Runtime.runScript, Runtime.callFunctionOn or Debugger.evaluateOnCallFrame. Its other Debugger methods need inspector.open() first.
Release builds of Bun do not include $vm, so BUN_JSC_useDollarVM has no effect on them. A debug build of Bun has it, and leaves it out at both levels of the flag.
Parts of Bun that stop working#
One part of Bun calls new Function() itself: the development server. Every other row is an API that compiles a string you give it.
| Part of Bun | Flag | =strict |
|---|---|---|
The development server: import() with import attributes of a module outside the bundle | throws | throws |
node:repl: repl.start(), REPLServer | works | throws |
node:worker_threads: new Worker(code, { eval: true }) | works | throws |
node:inspector: inspector.open() | works | throws |
bun:jsc: startRemoteDebugger() | works | throws |
node:vm: everything that takes source text | works | throws |
node:module: module._compile(), assigning Module.wrapper | works | throws |
Bun.plugin(): onLoad and build.module() that return source text | works | throws |
Every built-in module still loads with =strict. import { REPLServer } from "node:repl" is the one import that fails, because it reads REPLServer.
Tools that transpile through require.extensions, such as ts-node and @babel/register, do not work with =strict. Bun transpiles TypeScript and JSX itself, so a Bun program does not need them.
In a compiled executable#
Embed the flag when you compile. The executable then runs with it every time, with no arguments:
bun build --compile --compile-exec-argv="--disallow-code-generation-from-strings=strict" ./server.ts --outfile serverawait Bun.build({
entrypoints: ["./server.ts"],
compile: {
execArgv: ["--disallow-code-generation-from-strings=strict"],
outfile: "./server",
},
});Write the value with =. In --compile-exec-argv="--disallow-code-generation-from-strings strict", with a space, strict is a separate argument, which a compiled executable ignores. That executable runs with the flag and no value, and prints no error. The check in Checking that the flag is on catches the mistake.
BUN_OPTIONS can raise the level of a compiled executable. It cannot lower it.
A compiled executable does not read its own command line as Bun flags. ./server --preload ./x.js passes both arguments to the program in process.argv.
data: URLs written in the source#
The bundler resolves a data: URL that is written as a literal when it builds. That module is part of the executable, like any file the program imports, so it loads at both levels. A data: URL that the program puts together at run time is a string that becomes code, so =strict refuses it:
await import("data:text/javascript,export default 1"); // bundled at build time: loads
const url = ["data:text/javascript", "export default 1"].join(",");
await import(url); // made at run time: EvalErrorWithout --compile there is no build step, so =strict refuses both.
Checking that the flag is on#
process.execArgv contains the flag:
if (!process.execArgv.includes("--disallow-code-generation-from-strings=strict")) {
throw new Error("refusing to start without --disallow-code-generation-from-strings=strict");
}A Worker inherits process.execArgv, so the same check works inside one. When you give a Worker its own execArgv:
- With
=strict, Bun adds the flag to the list theWorkersees. - With the flag and no value, the
Workersees the list you gave it, as in Node.js. TheWorkerstill refuseseval()andnew Function().
What the flag does not cover#
- Files. A program that writes a file and then imports it runs that file. Importing files is what a program does, so the flag allows it. Use file system permissions to limit what the process can write.
- Startup. The entry point, a file given to
--preload,--importor--require, andbun -eare the program. So is what you type intobun repl. The flag applies to what the program does once it runs. Adata:URL given to--preload,--importor--requireis not a file, and=strictrefuses it like any otherdata:URL. BUN_OPTIONS. Bun readsBUN_OPTIONSin a compiled executable too. It cannot lower the level, but--preloadinBUN_OPTIONSloads a file before the entry point.BUN_BE_BUN. WithBUN_BE_BUN=1, a compiled executable runs as thebunCLI. The embedded program does not start, and the flags embedded with it do not apply.- Native code. A native addon, or a library loaded with
bun:ffi, can do what native code can do.--no-addonsrefusesprocess.dlopen()andcc()frombun:ffi, and--no-ffi-ccrefusescc(). Neither one refusesdlopen(),linkSymbols()orCFunction()frombun:ffi. - Child processes. A process that the program spawns has its own flags.