# 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.             |

```bash icon="terminal" terminal
bun --disallow-code-generation-from-strings=strict server.ts
```

```ts server.ts icon="/icons/typescript.svg"
new Function("return 1 + 1"); // EvalError: Code generation from strings disallowed for this context
```

A 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 `Worker` has the level of the process, whatever `execArgv` you give the `Worker`.
- Every realm and context the program creates later has it: `ShadowRealm`, `node:vm` contexts under `=strict`, and every [`Bun.ModuleGraph`](https://bun.aphrody.com/docs/runtime/module-graph).
- The level a [compiled executable](#in-a-compiled-executable) is built with is a floor. `BUN_OPTIONS` can 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 `Worker` starts, as in Node.js. `eval()` and `new Function()` throw inside it.
- With `=strict`, the `Worker` constructor throws the `EvalError`. 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](https://bun.aphrody.com/docs/runtime/plugins) 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](https://bun.aphrody.com/docs/bundler/executables).
- `new Worker()` with a file.
- `require()` and `createRequire()` of files and built-in modules.
- `Bun.ModuleGraph`.
- `JSON.parse()`, `RegExp`, and `eval()` of a value that is not a string.
- WebAssembly.
- Native addons and `bun:ffi`. See [What the flag does not cover](#what-the-flag-does-not-cover).
- A plugin that returns an `exports` object, or `contents` for a data loader such as `json` or `toml`.
- `vm.createContext()` and `new 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:

```txt
error: BUN_INSPECT cannot be used with --disallow-code-generation-from-strings=strict: the inspector evaluates code from strings
```

Editors 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:

```txt
warn: BUN_INSPECT_CONNECT_TO is ignored with --disallow-code-generation-from-strings=strict: the inspector evaluates code from strings
```

Every 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:

```bash icon="terminal" terminal
bun build --compile --compile-exec-argv="--disallow-code-generation-from-strings=strict" ./server.ts --outfile server
```

```ts build.ts icon="/icons/typescript.svg"
await 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](#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:

```ts server.ts icon="/icons/typescript.svg"
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: EvalError
```

Without `--compile` there is no build step, so `=strict` refuses both.

## Checking that the flag is on

`process.execArgv` contains the flag:

```ts server.ts icon="/icons/typescript.svg"
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 the `Worker` sees.
- With the flag and no value, the `Worker` sees the list you gave it, as in Node.js. The `Worker` still refuses `eval()` and `new Function()`.

## A build of Bun that always has `=strict`

When you build Bun from source, turning the `codeGenerationFromStrings` build option off makes `=strict` a compile-time constant:

```bash icon="terminal" terminal
bun scripts/build.ts --profile=release --codeGenerationFromStrings=off
```

That binary refuses what `--disallow-code-generation-from-strings=strict` refuses, with no flag:

- It reads no flag or environment variable to get the level, so `BUN_BE_BUN=1` and a missing `--compile-exec-argv` do not change it.
- The flag itself is still accepted, with or without `=strict`, and changes nothing.
- `process.execArgv` lists only the flags you gave, as in any build. To check for this build, see that `new vm.Script("")` throws an `EvalError` when you gave no flag.
- Bun's own code that compiles a string, such as `node:vm`'s and `module._compile()`'s, sits after a refusal that always happens, so an optimized build can leave it out.

JavaScriptCore's own `eval()` and `Function` code is still in the binary. It is switched off for every global object, and Bun has no code left that switches it on.

The option is on in the builds of Bun that we publish.

## 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`, `--import` or `--require`, and `bun -e` are the program. So is what you type into `bun repl`. The flag applies to what the program does once it runs. A `data:` URL given to `--preload`, `--import` or `--require` is not a file, and `=strict` refuses it like any other `data:` URL.
- **`BUN_OPTIONS`.** Bun reads `BUN_OPTIONS` in a compiled executable too. It cannot lower the level, but `--preload` in `BUN_OPTIONS` loads a file before the entry point.
- **`BUN_BE_BUN`.** With `BUN_BE_BUN=1`, a compiled executable runs as the `bun` CLI. 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-addons` refuses `process.dlopen()` and `cc()` from `bun:ffi`, and `--no-ffi-cc` refuses `cc()`. Neither one refuses `dlopen()`, `linkSymbols()` or `CFunction()` from `bun:ffi`.
- **Child processes.** A process that the program spawns has its own flags.
