GHSA-9CX6-37PM-9JFF

Vulnerability from github – Published: 2026-03-27 18:21 – Updated: 2026-03-30 20:08
VLAI?
Summary
Handlebars.js has Denial of Service via Malformed Decorator Syntax in Template Compilation
Details

Summary

When a Handlebars template contains decorator syntax referencing an unregistered decorator (e.g. {{*n}}), the compiled template calls lookupProperty(decorators, "n"), which returns undefined. The runtime then immediately invokes the result as a function, causing an unhandled TypeError: ... is not a function that crashes the Node.js process. Any application that compiles user-supplied templates without wrapping the call in a try/catch is vulnerable to a single-request Denial of Service.

Description

In lib/handlebars/compiler/javascript-compiler.js, the code generated for a decorator invocation looks like:

fn = lookupProperty(decorators, "n")(fn, props, container, options) || fn;

When "n" is not a registered decorator, lookupProperty(decorators, "n") returns undefined. The expression immediately attempts to call undefined as a function, producing:

TypeError: lookupProperty(...) is not a function

Because the error is thrown inside the compiled template function and is not caught by the runtime, it propagates up as an unhandled exception and — when not caught by the application — crashes the Node.js process.

This inconsistency is notable: references to unregistered helpers produce a clean "Missing helper: ..." error, while references to unregistered decorators cause a hard crash.

Attack scenario: An attacker submits {{*n}} as template content to any endpoint that calls Handlebars.compile(userInput)(). Each request crashes the server process; with process managers that auto-restart (PM2, systemd), repeated submissions create a persistent DoS.

Proof of Concept

const Handlebars = require('handlebars'); // Handlebars 4.7.8, Node.js v22.x

// Any of these payloads crash the process
Handlebars.compile('{{*n}}')({});
Handlebars.compile('{{*decorator}}')({});
Handlebars.compile('{{*constructor}}')({});

Expected crash output:

TypeError: lookupProperty(...) is not a function
    at Function.eval [as decorator] (eval at compile (...javascript-compiler.js:134:36))

Workarounds

  • Wrap compilation and rendering in try/catch: javascript try { const result = Handlebars.compile(userInput)(context); res.send(result); } catch (err) { res.status(400).send('Invalid template'); }
  • Validate template input before passing it to compile(). Reject templates containing decorator syntax ({{*...}}) if decorators are not used in your application.
  • Use the pre-compilation workflow: compile templates at build time and serve only pre-compiled templates; do not call compile() at request time.
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 4.7.8"
      },
      "package": {
        "ecosystem": "npm",
        "name": "handlebars"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.0.0"
            },
            {
              "fixed": "4.7.9"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33939"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-754"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-27T18:21:15Z",
    "nvd_published_at": "2026-03-27T22:16:20Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nWhen a Handlebars template contains decorator syntax referencing an unregistered decorator (e.g. `{{*n}}`), the compiled template calls `lookupProperty(decorators, \"n\")`, which returns `undefined`. The runtime then immediately invokes the result as a function, causing an unhandled `TypeError: ... is not a function` that crashes the Node.js process. Any application that compiles user-supplied templates without wrapping the call in a `try/catch` is vulnerable to a single-request Denial of Service.\n\n## Description\n\nIn `lib/handlebars/compiler/javascript-compiler.js`, the code generated for a decorator invocation looks like:\n\n```javascript\nfn = lookupProperty(decorators, \"n\")(fn, props, container, options) || fn;\n```\n\nWhen `\"n\"` is not a registered decorator, `lookupProperty(decorators, \"n\")` returns `undefined`. The expression immediately attempts to call `undefined` as a function, producing:\n\n```\nTypeError: lookupProperty(...) is not a function\n```\n\nBecause the error is thrown inside the compiled template function and is not caught by the runtime, it propagates up as an unhandled exception and \u2014 when not caught by the application \u2014 crashes the Node.js process.\n\nThis inconsistency is notable: references to unregistered **helpers** produce a clean `\"Missing helper: ...\"` error, while references to unregistered **decorators** cause a hard crash.\n\n**Attack scenario:** An attacker submits `{{*n}}` as template content to any endpoint that calls `Handlebars.compile(userInput)()`. Each request crashes the server process; with process managers that auto-restart (PM2, systemd), repeated submissions create a persistent DoS.\n\n## Proof of Concept\n\n```javascript\nconst Handlebars = require(\u0027handlebars\u0027); // Handlebars 4.7.8, Node.js v22.x\n\n// Any of these payloads crash the process\nHandlebars.compile(\u0027{{*n}}\u0027)({});\nHandlebars.compile(\u0027{{*decorator}}\u0027)({});\nHandlebars.compile(\u0027{{*constructor}}\u0027)({});\n```\n\nExpected crash output:\n```\nTypeError: lookupProperty(...) is not a function\n    at Function.eval [as decorator] (eval at compile (...javascript-compiler.js:134:36))\n```\n\n## Workarounds\n\n- **Wrap compilation and rendering in `try/catch`:**\n  ```javascript\n  try {\n    const result = Handlebars.compile(userInput)(context);\n    res.send(result);\n  } catch (err) {\n    res.status(400).send(\u0027Invalid template\u0027);\n  }\n  ```\n- **Validate template input** before passing it to `compile()`. Reject templates containing  decorator syntax (`{{*...}}`) if decorators are not used in your application.\n- **Use the pre-compilation workflow:** compile templates at build time and serve only pre-compiled  templates; do not call `compile()` at request time.",
  "id": "GHSA-9cx6-37pm-9jff",
  "modified": "2026-03-30T20:08:14Z",
  "published": "2026-03-27T18:21:15Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-9cx6-37pm-9jff"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33939"
    },
    {
      "type": "WEB",
      "url": "https://github.com/handlebars-lang/handlebars.js/commit/68d8df5a88e0a26fe9e6084c5c6aaebe67b07da2"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/handlebars-lang/handlebars.js"
    },
    {
      "type": "WEB",
      "url": "https://github.com/handlebars-lang/handlebars.js/releases/tag/v4.7.9"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Handlebars.js has Denial of Service via Malformed Decorator Syntax in Template Compilation"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…