CVE-2022-25967 (TypeScript, published 2023-01-30T05:15Z)
Description: value: Versions of the package eta before 2.0.0 are vulnerable to Remote Code Execution (RCE) by overwriting template engine configuration variables with view options received from The Express render API.**Note:** This is exploitable only for users who are rendering templates with user-defined data.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/eta-dev/eta
Files changed:
deno_dist/file-handlers.ts
src/file-handlers.ts
test/file-handlers.spec.ts
Commit message: Don't use data object for Eta configuration (#214)



Vulnerable code:
import { copyProps } from "./utils.ts";
  /** Express.js settings may be stored here */
  settings?: {
    [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
  };
 * - `renderFile(filename, dataAndConfig, [cb])`
 *   - Eta will merge `dataAndConfig` into `eta.config`
 * let rendered = await eta.renderFile("./template", {...data, cache: true})
  However, with Express, configuration options will be passed along with the data.
  Thus, Express will call renderFile with (filename, dataAndOptions, cb)
  And we want to also make (filename, data, options, cb) available
  data = data || {}; // If data is undefined, we don't want accessing data.settings to error
    // Otherwise, get the config from the data object
    // And then grab some config options from data.settings
    // Which is where Express sometimes stores them
    renderConfig = getConfig(data as PartialConfig) as EtaConfigWithFilename;
    if (data.settings) {
      // Pull a few things from known locations
      if (data.settings.views) {
        renderConfig.views = data.settings.views;
      }
      if (data.settings["view cache"]) {
        renderConfig.cache = true;
      }
      // Undocumented after Express 2, but still usable, esp. for
      // items that are unsafe to be passed along with data, like `root`
      const viewOpts = data.settings["view options"];

      if (viewOpts) {
        copyProps(renderConfig, viewOpts);
      }
    }
 * - `renderFile(filename, dataAndConfig, [cb])`
 *   - Eta will merge `dataAndConfig` into `eta.config`
 * let rendered = await eta.renderFile("./template", {...data, cache: true})
import { copyProps } from "./utils.js";
  /** Express.js settings may be stored here */
  settings?: {
    [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
  };
 * - `renderFile(filename, dataAndConfig, [cb])`
 *   - Eta will merge `dataAndConfig` into `eta.config`
 * let rendered = await eta.renderFile("./template", {...data, cache: true})
  However, with Express, configuration options will be passed along with the data.
  Thus, Express will call renderFile with (filename, dataAndOptions, cb)
  And we want to also make (filename, data, options, cb) available
  data = data || {}; // If data is undefined, we don't want accessing data.settings to error
    // Otherwise, get the config from the data object
    // And then grab some config options from data.settings
    // Which is where Express sometimes stores them
    renderConfig = getConfig(data as PartialConfig) as EtaConfigWithFilename;
    if (data.settings) {
      // Pull a few things from known locations
      if (data.settings.views) {
        renderConfig.views = data.settings.views;
      }
      if (data.settings["view cache"]) {
        renderConfig.cache = true;
      }
      // Undocumented after Express 2, but still usable, esp. for
      // items that are unsafe to be passed along with data, like `root`
      const viewOpts = data.settings["view options"];

      if (viewOpts) {
        copyProps(renderConfig, viewOpts);
      }
    }
 * - `renderFile(filename, dataAndConfig, [cb])`
 *   - Eta will merge `dataAndConfig` into `eta.config`
 * let rendered = await eta.renderFile("./template", {...data, cache: true})
    renderFile(fakeFilePath, { cache: true }, function (_err: Error | null, res?: string) {

  it("parses a simple template w/ settings from Express", async () => {
    renderFile(
      filePath,
      {
        name: "<p>Ben</p>",
        cache: true,
        settings: {
          views: [path.join(__dirname, "templates"), path.join(__dirname, "othertemplates")],
          "view cache": true,
          "view options": { autoEscape: false },
        },
      },
      function (_err: Error | null, res?: string) {
        expect(res).toEqual("Hi <p>Ben</p>");
      }
    );
  });
    function cb(err: Error, _res?: string) {
      expect(err.message).toMatch(
    renderFile(errFilePath, { name: "Ada Lovelace", async: true }, cb);

Fixed code:
 * - `renderFile(filename, data, [cb])`
  Express will call renderFile with (filename, data, cb)
  We also want to make (filename, data, options, cb) available
  data = data || {};
    // Otherwise, get the default config
    renderConfig = getConfig({}) as EtaConfigWithFilename;
 * - `renderFile(filename, data, [cb])`
 * - `renderFile(filename, data, [cb])`
  Express will call renderFile with (filename, data, cb)
  We also want to make (filename, data, options, cb) available
  data = data || {};
    // Otherwise, get the default config
    renderConfig = getConfig({}) as EtaConfigWithFilename;
 * - `renderFile(filename, data, [cb])`
    renderFile(fakeFilePath, {}, { cache: true }, function (_err: Error | null, res?: string) {
    function cb(err: Error | null, _res?: string) {
      expect(err?.message).toMatch(
    renderFile(errFilePath, { name: "Ada Lovelace" }, { async: true }, cb);