Back to Blog
critical SEVERITY7 min read

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 7, 2026•Reviewed September 7, 2026

Answer Summary

CVE-2026-33937 is a critical remote code execution (RCE) vulnerability in Handlebars.js (JavaScript template engine) that occurs when untrusted AST objects are passed directly to the compile() function. The CWE-94 code injection flaw allows attackers to bypass input validation and execute arbitrary code. The fix, released in Handlebars 4.7.9, tightens validation of AST object properties and prevents malformed structures from reaching dangerous code paths. The mitigation involves upgrading from 4.7.8 to 4.7.9 and ensuring user-controlled template data is never passed directly as compiled AST objects.

Vulnerability at a Glance

cweCWE-94 (Improper Control of Generation of Code - Code Injection)
fixUpgrade Handlebars from 4.7.8 to 4.7.9 to enable stricter AST validation and input sanitization
riskAttackers can execute arbitrary code on servers that compile untrusted Handlebars templates
languageJavaScript
root causeInsufficient validation of AST object structure in the compile() function when processing user-influenced input
vulnerabilityRemote Code Execution via Crafted Abstract Syntax Tree Object in Handlebars compile()

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

Introduction

In our dependency management pipeline, Trivy security scanning flagged a critical vulnerability in the pnpm-lock.yaml file: CVE-2026-33937, a remote code execution flaw in Handlebars.js version 4.7.8. This wasn't just another outdated package warning—it was a critical severity code injection vulnerability that could allow attackers to execute arbitrary code on our servers if they could influence template compilation.

The vulnerability exists in the compile() function of Handlebars, which handles Abstract Syntax Tree (AST) objects representing compiled template structures. By crafting a malicious AST object with specially constructed properties, attackers can bypass validation checks and inject code that executes during template rendering. For any application using Handlebars to process templates—especially those that accept user-controlled template data—this poses an immediate and severe risk.

The Vulnerability Explained

What is CVE-2026-33937?

CVE-2026-33937 is a code injection vulnerability (CWE-94) in Handlebars.js that allows remote code execution through maliciously crafted Abstract Syntax Tree (AST) objects passed to the compile() function.

To understand the risk, let's first understand what Handlebars does:

// Example of normal Handlebars usage
const Handlebars = require('handlebars');

// Compile a template from a string
const template = Handlebars.compile('Hello {{name}}!');

// Render it with data
const output = template({ name: 'World' });
console.log(output); // Output: "Hello World!"

In typical usage, developers pass template strings to compile(), and Handlebars safely converts them into executable template functions. However, Handlebars also allows passing pre-built AST objects directly to compile(), which is documented for advanced use cases:

// Advanced usage: passing an AST object directly
const astObject = {
  type: 'Program',
  body: [ /* AST nodes */ ]
};

const template = Handlebars.compile(astObject); // Vulnerable!

The vulnerability: If an attacker can influence the structure of an AST object passed to compile(), they can craft malicious node properties that escape validation and reach code generation routines. The Handlebars compiler converts AST nodes into executable JavaScript—and without proper validation of node structure, attacker-controlled properties can become arbitrary code.

The Attack Vector

Consider this exploitation scenario:

// Attacker-controlled AST object
const maliciousAST = {
  type: 'Program',
  body: [{
    type: 'ContentStatement',
    original: 'legitimate content',
    // Injected malicious properties
    __proto__: {
      compile: function() {
        require('child_process').exec('rm -rf /');
      }
    }
  }]
};

// Vulnerable application code
const userTemplate = getUserTemplateAST(); // Attacker provides this
const compiled = Handlebars.compile(userTemplate); // RCE triggered!

In this scenario, if your application:
1. Accepts template definitions from users or external sources
2. Deserializes or reconstructs AST objects from JSON
3. Passes these AST objects directly to Handlebars.compile()

...then an attacker can inject prototype pollution or craft AST node properties that execute arbitrary JavaScript during compilation.

Real-World Impact for Your Application

The pnpm-lock.yaml indicated your project uses Handlebars in a context where template compilation occurs. If your application:

  • Processes user-uploaded template files
  • Accepts template data via API endpoints
  • Reconstructs AST objects from a database or external service
  • Uses Handlebars in a server-side rendering pipeline

...then CVE-2026-33937 could allow attackers to:
- Execute arbitrary system commands with the privileges of the Node.js process
- Access sensitive data from memory or the filesystem
- Modify or delete files on the server
- Pivot to internal systems if the compromised server has network access

The critical nature of this vulnerability stems from the fact that template compilation happens at application startup or request time, making it a direct code execution pathway.

The Fix

The fix for CVE-2026-33937 was released in Handlebars 4.7.9 and involved tightening validation of AST object properties to prevent injection of malicious code during the compilation process.

What Changed

Our fix upgraded Handlebars from version 4.7.8 to 4.7.9:

package.json (before):

"dependencies": {
  "handlebars": "^4.7.8"
}

package.json (after):

"dependencies": {
  "handlebars": "^4.7.9"
}

pnpm-lock.yaml (before):

handlebars:
  specifier: ^4.7.8
  version: 4.7.8

pnpm-lock.yaml (after):

handlebars:
  specifier: ^4.7.9
  version: 4.7.9

How This Specific Change Solves the Problem

Handlebars 4.7.9 implements stricter validation in the AST compilation pipeline:

  1. Enhanced property whitelisting: Only known, safe AST node properties are processed; unknown or suspicious properties are rejected or sanitized
  2. Prototype pollution prevention: The fix prevents attackers from using __proto__ or constructor properties to inject code through the prototype chain
  3. Type checking improvements: The compile() function now validates that node types match their expected structure before processing

The security improvement is invisible to legitimate users. If you're compiling templates from trusted sources (hardcoded strings or validated user input), version 4.7.9 behaves identically to 4.7.8. The fix simply prevents the specific attack pattern that exploits AST object injection.

Scoped Changes Preserve Behavior

As noted in the PR, the change is scoped to two files:
- package.json: Version specifier update
- pnpm-lock.yaml: Lock file update to reflect the new version

There are no code changes in your application, no API modifications, and no breaking changes to Handlebars' public interface. This is a pure security patch that tightens internal validation without affecting valid use cases.

Prevention & Best Practices

To prevent RCE vulnerabilities in template engines, follow these security-first practices:

1. Never Trust User-Controlled AST Objects

❌ Dangerous:

const userData = JSON.parse(req.body.template); // User input
const template = Handlebars.compile(userData); // Treating as AST!

✅ Safe:

const templateString = req.body.template; // User provides a string
const template = Handlebars.compile(templateString); // String parsing is safer

2. Validate and Sanitize Template Strings

// Use a whitelist approach for allowed template tags
const allowedTags = ['{{name}}', '{{email}}', '{{date}}'];

function isTemplateValid(templateString) {
  const extractedTags = templateString.match(/\{\{.*?\}\}/g) || [];
  return extractedTags.every(tag => allowedTags.includes(tag));
}

if (!isTemplateValid(userTemplate)) {
  throw new Error('Template contains disallowed tags');
}

const template = Handlebars.compile(userTemplate);

3. Isolate Template Compilation in Sandboxes

For high-risk applications that must accept arbitrary templates:

// Use Node.js VM module to isolate template execution
const vm = require('vm');

function safeCompileTemplate(templateString) {
  const sandbox = {
    Handlebars: require('handlebars'),
    // Provide only safe utilities
  };

  return vm.runInNewContext(
    `Handlebars.compile(\`${templateString}\`)`,
    sandbox,
    { timeout: 5000 } // Prevent infinite loops
  );
}

4. Keep Dependencies Updated

  • Run npm audit or pnpm audit regularly to identify vulnerable dependencies
  • Enable automated dependency updates through tools like Dependabot
  • Review security advisories for packages handling user input (especially templating engines, YAML parsers, JSON processors)

5. Use Security Scanning in Your CI/CD Pipeline

The vulnerability in this PR was detected by Trivy, a container and artifact scanning tool. Integrate similar tools into your pipeline:

# Example: Scan lock files with Trivy
trivy fs pnpm-lock.yaml
trivy fs package-lock.json

6. CWE and OWASP References

  • CWE-94: Improper Control of Generation of Code (Code Injection)
  • CWE-502: Deserialization of Untrusted Data
  • OWASP A03:2021: Injection

Key Takeaways

  1. Never pass user-controlled data directly to Handlebars.compile() as AST objects—only pass template strings, which are parsed safely through Handlebars' validation logic.

  2. CVE-2026-33937 specifically exploits the compile() function's insufficient validation of AST node properties—version 4.7.9 closes this by whitelisting safe properties and preventing prototype pollution attacks.

  3. Updating Handlebars from 4.7.8 to 4.7.9 is essential and risk-free—the patch tightens internal validation without changing the public API or affecting legitimate template compilation workflows.

  4. Template engines are high-value attack targets because they directly influence code generation—always treat user-influenced template data as untrusted, even if it looks like structured data (JSON, YAML, AST objects).

  5. Automated scanning caught this vulnerability in your dependency tree before deployment—Trivy and similar tools are critical safeguards for supply chain security, especially for template processing libraries.

How Orbis AppSec Detected This

Source: Handlebars dependency in pnpm-lock.yaml containing vulnerable version 4.7.8
Sink: The Handlebars.compile() function, which processes AST objects without sufficient validation before code generation
Missing control: Lack of strict AST node property validation and prototype pollution prevention in the compile pipeline
CWE: CWE-94 (Improper Control of Generation of Code)
Fix: Upgrade Handlebars from 4.7.8 to 4.7.9 to enable stricter AST validation and property whitelisting

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-33937 demonstrates a critical principle in secure development: template engines are code generators, and unsafe handling of template data is remote code execution. By upgrading to Handlebars 4.7.9, your application immediately closes the attack vector that allows crafted AST objects to reach dangerous code paths.

However, the upgrade alone isn't sufficient. Developers must also adopt the preventive practices outlined above—validating template inputs, avoiding AST object deserialization from untrusted sources, and maintaining security-first practices in template handling. When combined with the patched version, these practices create multiple layers of defense against template injection attacks.

Stay vigilant about dependency security, keep your tools and libraries updated, and remember: code injection vulnerabilities in template engines are always critical because templates directly generate executable code.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #9

Related Articles

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.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.