GitHub Security Advisory: GHSA-6q6h-j7hj-3r64
Package: happy-dom (NPM)
Severity: HIGH (CVSS 8.8)
CWE: CWE-94: Improper Control of Generation of Code ('Code Injection')
Vulnerable versions: >= 15.10.0, <= 20.8.7
Patched in: 20.8.8

Summary: Happy DOM ECMAScriptModuleCompiler: unsanitized export names are interpolated as executable code

Details:
### Summary

A code injection vulnerability in `ECMAScriptModuleCompiler` allows an attacker to achieve Remote Code Execution (RCE) by injecting arbitrary JavaScript expressions inside `export { }` declarations in ES module scripts processed by happy-dom. The compiler directly interpolates unsanitized content into generated code as an executable expression, and the quote filter does not strip backticks, allowing template literal-based payloads to bypass sanitization.

### Details

**Vulnerable file**: `packages/happy-dom/src/module/ECMAScriptModuleCompiler.ts`, lines 371-385

The "Export object" handler extracts content from `export { ... }` using the regex `export\s*{([^}]+)}`, then generates executable code by directly interpolating it:

    } else if (match[16] && isTopLevel && PRECEDING_STATEMENT_TOKEN_REGEXP.test(precedingToken)) {
        // Export object
        const parts = this.removeMultilineComments(match[16]).split(/\s*,\s*/);
        const exportCode: string[] = [];
        for (const part of parts) {
            const nameParts = part.trim().split(/\s+as\s+/);
            const exportName = (nameParts[1] || nameParts[0]).replace(/["']/g, '');
            const importName = nameParts[0].replace(/["']/g, '');  // backticks NOT stripped
            if (exportName && importName) {
                exportCode.push(`$happy_dom.exports['${exportName}'] = ${importName}`);
                //               importName is inserted as executable code, not as a string
            }
        }
        newCode += exportCode.join(';\n');
    }

The issue has three root causes:

1. `STATEMENT_REGEXP` uses `{[^}]+}` which matches **any content** inside braces, not just valid JavaScript identifiers
2. The captured `importName` is placed in **code context** (as a JS expression to evaluate), not in string context
3. `.replace(/["']/g, '')` strips `"` and `'` but **not backticks**, so template literal strings like `` `child_process` `` survive the filter

**Attack flow:**

    Source:     export { require(`child_process`).execSync(`id`) }

    Regex captures match[16] = " require(`child_process`).execSync(`id`) "

    After .replace(/["']/g, ''):
      importName = "require(`child_process`).execSync(`id`)"
      (backticks are preserved)

    Generated code:
      $happy_dom.exports["require(`child_process`).execSync(`id`)"] = require(`child_process`).execSync(`id`)

    evaluateScript() executes this code -> RCE

**Note**: This is a different vulnerability from CVE-2024-51757 (SyncFetchScriptBuilder injection) and CVE-2025-61927 (VM context escape). Those were patched in v15.10.2 and v20.0.0 respectively, but this vulnerable code path in `ECMAScriptModuleCompiler` remains present in v20.8.4 (latest). In v20.0.0+ where JavaScript evaluation is disabled by default, this vulnerability is exploitable when JavaScript evaluation is explicitly enabled by the user.

### PoC

**Standalone PoC script** — reproduces the vulnerability without installing happy-dom by