Skip to content

--disallow-code-generation-from-strings breaks named import of re-exported CommonJS export #61078

Description

@Mysak0CZ

Version

v25.2.1

Platform

Linux [...] 6.12.62-1-MANJARO #1 SMP PREEMPT_DYNAMIC Fri, 12 Dec 2025 19:00:00 +0000 x86_64 GNU/Linux

Subsystem

No response

What steps will reproduce the bug?

Minimal example with three files, with a.mjs being entrypoint:

a.mjs:

import { Hello } from "./b.cjs";

Hello();

b.cjs:

module.exports = require("./c.cjs");

c.cjs:

function Hello() {
    console.log("Hello, World!");
}

module.exports = { Hello };

How often does it reproduce? Is there a required condition?

With default flags works without issues, but if using --disallow-code-generation-from-strings the application breaks.

What is the expected behavior? Why is that the expected behavior?

The application should work as intended, as it doesn't use eval nor new Function:

$ node a.mjs
Hello, World!

What do you see instead?

The application fails to run:

$ node --disallow-code-generation-from-strings a.mjs
file://[...]/a.mjs:1
import { Hello } from "./b.cjs";
         ^^^^^
SyntaxError: Named export 'Hello' not found. The requested module './b.cjs' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from './b.cjs';
const { Hello } = pkg;

    at #asyncInstantiate (node:internal/modules/esm/module_job:302:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:405:5)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:654:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v25.2.1

Additional information

Note, that the above example is a simplification of real-world usecase, where a.mjs comes from an ESM application that imports b.cjs CommonJS library, which uses re-exports internally.
Most notable case I stumbled upon of this happening is while using express.

There is a workaround for the application:
a_workaround.mjs:

import b from "./b.cjs";
const { Hello } = b;

Hello();

But this workaround is not always applicable (e.g. if using an ESM dependency outside our control that imports such CommonJS dependency itself) and from the meaning --disallow-code-generation-from-strings it is not clear why it should be needed. Even if the workaround is possible, the bug hampers usage of this hardening flag, even if the application nor the libraries make use of eval-like features.

Activity

  1. guybedford commented on Dec 16, 2025

    @guybedford
    Contributor

    This is likely due to cjs-module-lexer using eval() to read strings (see https://github.com/nodejs/cjs-module-lexer/blob/main/src/lexer.js#L50). Replacing this with a non-eval based string parser approach would be needed to support this.

  2. linked a pull request that will close this issuedeps: update cjs-module-lexer to 2.2.0 #61271on Jan 4, 2026
  3. bakkot commented on Jan 6, 2026

    @bakkot
    Contributor

    I was hoping that this would be fixed by #61271, but trying on the latest nightly it does not seem to be.

    Edit: though actually when I build it locally it does work? So, maybe I'm just confused.

  4. guybedford commented on Jan 7, 2026

    @guybedford
    Contributor

    @bakkot I just tested this on the latest main today and the test case fails in release Node and passes in main Node now in my testing. Obviously nice to actually add the test case of course, but I can confirm it resolved the issue.

  5. bitbytebit1 commented on Jan 8, 2026

    @bitbytebit1

    Very curious when this will see public release, any ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions