Back to Blog
critical SEVERITY9 min read

Critical RCE in Handlebars.js: How CVE-2026-33937 Was Fixed

A critical Remote Code Execution vulnerability (CVE-2026-33937) was discovered in Handlebars.js that allows attackers to execute arbitrary code by crafting malicious Abstract Syntax Tree objects passed to the `compile()` function. This post breaks down how the vulnerability works, why it's dangerous, and how upgrading to Handlebars 4.7.9 closes the attack vector.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published May 8, 2026•Reviewed September 20, 2026

Answer Summary

CVE-2026-33937 is a critical Remote Code Execution (RCE) vulnerability in Handlebars.js (CWE-94: Improper Control of Generation of Code) where a specially crafted Abstract Syntax Tree (AST) object passed to the `compile()` function allows attackers to execute arbitrary JavaScript outside the template sandbox. The root cause is that Handlebars trusted the structure of AST objects without validating node types, property names, or prototype chain integrity before generating executable code from them. The fix, released in Handlebars 4.7.9, adds strict AST node validation that rejects any AST containing unexpected node types, disallowed property names, or prototype-polluted properties before compilation begins.

Vulnerability at a Glance

cweCWE-94 (Improper Control of Generation of Code / Code Injection)
fixAdded strict AST node type and property validation in Handlebars 4.7.9 before any code generation occurs
riskArbitrary code execution on the server hosting the Handlebars template engine
languageJavaScript / Node.js
root causeThe compile() function consumed attacker-controlled AST objects without validating node types or property integrity, enabling sandbox escape
vulnerabilityRemote Code Execution via malicious AST object in Handlebars.js compile()

Critical RCE in Handlebars.js: Understanding and Fixing CVE-2026-33937

TL;DR: A critical Remote Code Execution vulnerability in Handlebars.js (CVE-2026-33937) allows attackers to execute arbitrary code through crafted Abstract Syntax Tree (AST) objects. The fix is straightforward: upgrade to Handlebars 4.7.9.


Introduction

If your Node.js application uses Handlebars.js for templating — and millions do — you need to pay attention to CVE-2026-33937. This critical-severity vulnerability allows an attacker to achieve Remote Code Execution (RCE) by passing a specially crafted Abstract Syntax Tree (AST) object to Handlebars' compile() function.

RCE vulnerabilities are among the most severe class of security issues. Unlike XSS or CSRF, which typically require a victim to take an action in a browser, RCE means an attacker can run their own code directly on your server. That's game over for confidentiality, integrity, and availability.

Handlebars.js is downloaded tens of millions of times per week on npm. It's embedded in build tools, static site generators, email templating engines, and countless web applications. A vulnerability here has a massive blast radius.


The Vulnerability Explained

What Is Handlebars.js?

Handlebars.js is a popular JavaScript templating library that lets you build semantic templates. You write templates like:

<h1>Hello, {{name}}!</h1>
<p>You have {{messages.length}} new messages.</p>

Handlebars compiles these templates into JavaScript functions that can be called with a data context. This compilation step is at the heart of this vulnerability.

What Is an Abstract Syntax Tree (AST)?

When Handlebars parses a template string, it first converts it into an Abstract Syntax Tree (AST) — a structured, tree-like representation of the template's syntax. The compile() function can accept either a raw template string or a pre-parsed AST object.

This dual-input capability is where things get dangerous.

How the Vulnerability Works

The vulnerability exists in how Handlebars' compile() function processes AST objects. When an attacker can control or influence the AST object passed to compile(), they can craft a malicious AST that, when processed by the compiler, causes arbitrary JavaScript code to be generated and executed.

Here's a simplified conceptual illustration of the attack:

const Handlebars = require('handlebars');

// A normal, safe template compilation
const safeTemplate = Handlebars.compile("Hello, {{name}}!");
safeTemplate({ name: "World" }); // "Hello, World!"

// A crafted malicious AST object (conceptual — CVE-2026-33937)
const maliciousAST = {
  type: "Program",
  body: [{
    type: "MustacheStatement",
    // Attacker-controlled node properties that trick the compiler
    // into generating and executing arbitrary code...
    path: {
      type: "PathExpression",
      // Crafted payload that escapes the template sandbox
      original: "__proto__",
      // ... additional crafted properties
    }
  }]
};

// If an application passes untrusted AST objects to compile(),
// the attacker's code runs on the server
const dangerous = Handlebars.compile(maliciousAST);
dangerous({}); // 💥 Arbitrary code execution

The root cause is insufficient validation of AST node properties before they are used to generate JavaScript code. The compiler trusts the structure and content of the AST too liberally, allowing carefully constructed node objects to break out of the intended template execution context.

Attack Scenarios

Scenario 1: Applications Accepting User-Provided Templates

This is the most direct attack path. If your application allows users to submit templates that are then compiled server-side (e.g., a custom email template builder, a report generator, or a CMS with template editing), an attacker could submit a template that, when parsed and compiled, produces a malicious AST.

// ⚠️ DANGEROUS: Never compile untrusted user input server-side
app.post('/preview-template', (req, res) => {
  const userTemplate = req.body.template; // Attacker controls this
  const compiled = Handlebars.compile(userTemplate); // 💥 Potential RCE
  res.send(compiled({}));
});

Scenario 2: Deserialization of Stored AST Objects

Some applications optimize performance by pre-parsing templates into AST objects and storing them (in a database, cache, or file system). If an attacker can tamper with those stored AST objects — through a separate injection vulnerability, a compromised cache, or an insecure deserialization pathway — they can plant a malicious AST that gets executed later.

// ⚠️ DANGEROUS: Loading AST from untrusted/unvalidated storage
const cachedAST = JSON.parse(redis.get('template:welcome')); // Attacker tampered with cache
const compiled = Handlebars.compile(cachedAST); // 💥 Malicious AST executed

Scenario 3: Supply Chain / Dependency Confusion

In complex build pipelines, AST objects might be passed between tools or plugins. A compromised upstream package that produces malicious AST output could trigger RCE in any downstream consumer using a vulnerable Handlebars version.

Real-World Impact

Impact Category Description
Confidentiality Attacker can read files, environment variables, secrets, and database credentials
Integrity Attacker can modify files, plant backdoors, or corrupt data
Availability Attacker can crash the server, delete files, or launch resource exhaustion attacks
Lateral Movement Compromised server can be used to attack internal network resources

This is why this vulnerability is rated CRITICAL. A single successful exploit can lead to complete server compromise.


The Fix

What Changed

The fix was implemented by upgrading Handlebars from version 4.7.8 to 4.7.9. The changes were applied to package.json and pnpm-lock.yaml to ensure the updated version is locked in across all environments.

Before (vulnerable):

// package.json
{
  "dependencies": {
    "handlebars": "4.7.8"
  }
}

After (fixed):

// package.json
{
  "dependencies": {
    "handlebars": "4.7.9"
  }
}

For projects using pnpm, the lock file (pnpm-lock.yaml) was also updated to ensure the resolved package hash points to the patched version:

# pnpm-lock.yaml (simplified)
# Before:
handlebars:
  version: 4.7.8
  resolution: {integrity: sha512-...oldHash...}

# After:
handlebars:
  version: 4.7.9
  resolution: {integrity: sha512-...newHash...}

How the Fix Solves the Problem

Version 4.7.9 introduces stricter validation of AST node properties within the compile() function. Specifically, the patch:

  1. Validates node types against an allowlist — Only recognized, safe AST node types are processed. Unknown or unexpected node structures are rejected before code generation begins.

  2. Sanitizes node property values — Properties that are used in code generation are validated to ensure they cannot contain executable code or escape the template sandbox.

  3. Hardens the code generation phase — The JavaScript code generator applies additional escaping and boundary checks to ensure that even if a malformed node slips through, it cannot produce executable code outside the intended template scope.

The key principle is never trust input, even structured input. An AST object looks like safe, structured data — but its contents can be just as dangerous as raw user input if not properly validated.


Tooling Recommendations

Tool Purpose How It Helps
Trivy Vulnerability scanning Detected this CVE in pnpm-lock.yaml
Snyk Continuous monitoring Alerts on new CVEs in your dependencies
Dependabot Automated PRs Auto-creates PRs for vulnerable dependencies
Socket.dev Supply chain security Detects malicious packages before install
npm audit Built-in scanning Quick check for known vulnerabilities

Conclusion

CVE-2026-33937 is a stark reminder that templating engines are powerful tools that must be treated with care. The ability to compile templates into executable JavaScript functions is incredibly useful — but it also means that malicious input to the compilation pipeline can have catastrophic consequences.

The key takeaways from this vulnerability are:

  1. Update immediately — Upgrade to Handlebars 4.7.9 if you haven't already. This is a critical fix.
  2. Never compile untrusted input — If users can influence what gets passed to compile(), you have a potential RCE on your hands.
  3. Treat ASTs as untrusted data — Structured data is not inherently safe. Validate it.
  4. Automate your security scanning — This vulnerability was caught by an automated scanner (Trivy). Invest in tooling that continuously monitors your dependency tree.
  5. Defense in depth — Combine dependency updates with least-privilege execution, input validation, and runtime monitoring.

The fact that this fix was identified, verified, and deployed through an automated security pipeline demonstrates exactly how modern security practices should work. Vulnerabilities will always be discovered — what matters is how quickly and reliably you can respond.

Stay secure, keep your dependencies updated, and never stop learning. 🔒


This vulnerability was identified by OrbisAI Security automated scanning and remediated via an automated security fix pipeline. The fix was verified with a Trivy re-scan and LLM-assisted code review.


References

Prevention and further reading

Related Articles

medium

path-to-regexp 0.1.12 DoS: Catastrophic Backtracking on Malformed URL

path-to-regexp version 0.1.12 contains a regular expression engine vulnerability where maliciously crafted URL parameters trigger exponential backtracking, causing CPU exhaustion and service unavailability. Upgrading to version 0.1.13 eliminates the vulnerable pattern from the parsing logic.

critical

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

high

MapManager.get() Race Condition Duplicates API Requests

The MapManager's `get(mapUid, cache)` method used a check-then-act pattern that permitted multiple concurrent requests to pass the cache miss check simultaneously, triggering redundant API calls and risking cache corruption. The fix introduces a `_pending` promise map to deduplicate in-flight fetches for identical map UIDs.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.