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
Published September 7, 2026Reviewed 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.


References

Frequently Asked Questions

What is a code injection vulnerability in template engines?

Code injection in template engines occurs when attackers can inject malicious code that gets executed during template compilation or rendering. In Handlebars, this happens when untrusted data structures (particularly AST objects) bypass validation and reach code generation routines.

How do you prevent RCE in Handlebars template processing?

Never pass user-controlled data directly to compile() as AST objects; always validate and sanitize template strings before compilation, use allowlist patterns for dynamic template generation, and keep Handlebars updated to receive security patches like 4.7.9.

What CWE is this vulnerability?

CWE-94: Improper Control of Generation of Code (Code Injection). This CWE category covers flaws where user input flows into code generation or execution mechanisms without proper validation.

Is keeping Handlebars updated enough to prevent this RCE?

Updating to 4.7.9 fixes this specific vulnerability, but it's not a complete solution. You must also audit your code to ensure user-controlled data never reaches compile() as AST objects, and implement input validation at application boundaries.

Can static analysis detect this vulnerability?

Yes, security scanners like Trivy (used in this PR) can detect known vulnerable versions of Handlebars in lock files. However, detecting exploitable code patterns requires taint analysis tools that trace user input flowing into compile() calls with AST arguments.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #9

Related Articles

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.

critical

How Denial of Service via Gzip Bomb happens in Node.js and how to fix it

A critical Denial of Service vulnerability (CVE-2026-59873) in the `tar` npm package allowed attackers to craft malicious gzip archives that could exhaust memory or CPU during decompression. The fix upgrades `tar` from 7.5.11 to 7.5.21 across `package.json` and `package-lock.json`, closing the resource-exhaustion path without changing any application code.

critical

How SQL Injection happens in PHP bulk email systems and how to fix it

A critical SQL injection vulnerability in `admin/utilities/bulkEmailSystem.php` allowed attackers to inject arbitrary SQL through unvalidated database names passed from user input. The fix implements strict input validation using regex pattern matching to ensure only safe database identifiers are processed, preventing exploitation of the bulk email functionality.