Skip to main content

EUVDEUVD-2026-16860

| CVE-2026-33940 HIGH
Code Injection (CWE-94)
2026-03-27 https://github.com/handlebars-lang/handlebars.js GHSA-xhpv-hc6g-r9c6
8.1
CVSS 3.1 · Vendor: https://github.com/handlebars-lang/handlebars.js
Share

Severity by source

Vendor (https://github.com/handlebars-lang/handlebars.js) PRIMARY
8.1 HIGH
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Red Hat
8.1 HIGH
qualitative

Primary rating from Vendor (https://github.com/handlebars-lang/handlebars.js).

CVSS VectorVendor: https://github.com/handlebars-lang/handlebars.js

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

Lifecycle Timeline

4
EUVD ID Assigned
Mar 27, 2026 - 18:30 euvd
EUVD-2026-16860
Analysis Generated
Mar 27, 2026 - 18:30 vuln.today
Patch released
Mar 27, 2026 - 18:30 nvd
Patch available
CVE Published
Mar 27, 2026 - 18:21 nvd
HIGH 8.1

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 3 npm packages depend on handlebars (1 direct, 2 indirect)

Ecosystem-wide dependent count for version 4.0.0.

DescriptionCVE.org

Summary

A crafted object placed in the template context can bypass all conditional guards in resolvePartial() and cause invokePartial() to return undefined. The Handlebars runtime then treats the unresolved partial as a source that needs to be compiled, passing the crafted object to env.compile(). Because the object is a valid Handlebars AST containing injected code, the generated JavaScript executes arbitrary commands on the server. The attack requires the adversary to control a value that can be returned by a dynamic partial lookup.

Description

The vulnerable code path spans two functions in lib/handlebars/runtime.js:

resolvePartial(): A crafted object with call: true satisfies the first branch condition (partial.call) and causes an early return of the original object itself, because none of the remaining conditionals (string check, options.partials lookup, etc.) match a plain object. The function returns the crafted object as-is.

invokePartial(): When resolvePartial returns a non-function object, invokePartial produces undefined. The runtime interprets undefined as "partial not yet compiled" and calls env.compile(partial, ...) where partial is the crafted AST object. The JavaScript code generator processes the AST and emits JavaScript containing the injected payload, which is then evaluated.

Minimum prerequisites:

  1. The template uses a dynamic partial lookup: {{> (lookup . "key")}} or equivalent.
  2. The adversary can set the value of the looked-up context property to a crafted object.

In server-side rendering scenarios where templates process user-supplied context data, this enables full Remote Code Execution.

Proof of Concept

javascript
const Handlebars = require('handlebars');

const vulnerableTemplate = `{{> (lookup . "payload")}}`;

const maliciousContext = {
  payload: {
    call: true, // bypasses the primary resolvePartial branch
    type: "Program",
    body: [
      {
        type: "MustacheStatement",
        depth: 0,
        path: {
          type: "PathExpression",
          parts: ["pop"],
          original: "this.pop",
          // Injected code breaks out of the generated function's argument list
          depth: "0])),function () {console.error('VULNERABLE: object -> dynamic partial -> RCE');}()));//",
        },
      },
    ],
  },
};

Handlebars.compile(vulnerableTemplate)(maliciousContext);
// Prints: VULNERABLE: object -> dynamic partial -> RCE

Workarounds

  • Use the runtime-only build (require('handlebars/runtime')). Without compile(), the fallback compilation path in invokePartial is unreachable.
  • Sanitize context data before rendering: ensure no value in the context is a non-primitive object that could be passed to a dynamic partial.
  • Avoid dynamic partial lookups ({{> (lookup ...)}}) when context data is user-controlled.

AnalysisAI

Remote code execution in Handlebars templating engine (npm package) allows unauthenticated network attackers to execute arbitrary server-side commands by exploiting dynamic partial resolution logic. Affected versions include all releases prior to v4.7.9. Attack requires the adversary to control context data passed to templates that use dynamic partial lookups. A proof-of-concept exploit demonstrates arbitrary code execution and is publicly documented. CVSS score of 8.1 reflects high complexity due to the need for specific template patterns and attacker-controlled context values.

Technical ContextAI

Handlebars is a widely-deployed JavaScript templating engine (pkg:npm/handlebars) used for server-side and client-side HTML generation. This vulnerability exploits CWE-94 (Improper Control of Generation of Code, 'Code Injection') through a logic flaw in the partial resolution mechanism. When resolvePartial() processes a crafted object with a 'call: true' property in lib/handlebars/runtime.js, it bypasses all conditional guards and returns the object unchanged. The subsequent invokePartial() function misinterprets this non-function object as an uncompiled template source and passes it to env.compile(). Because the crafted object is structured as a valid Handlebars Abstract Syntax Tree containing injected JavaScript code fragments, the compilation step generates and executes arbitrary attacker-controlled commands. The vulnerability chain requires dynamic partial lookups (e.g., {{> (lookup . "key")}}) combined with attacker-influenced template context data.

RemediationAI

Upgrade Handlebars to version v4.7.9 or later, as documented in the release notes at https://github.com/handlebars-lang/handlebars.js/releases/tag/v4.7.9 and patched via commit 68d8df5a88e0a26fe9e6084c5c6aaebe67b07da2 available at https://github.com/handlebars-lang/handlebars.js/commit/68d8df5a88e0a26fe9e6084c5c6aaebe67b07da2. Organizations unable to immediately upgrade should implement one of three workarounds: migrate to the runtime-only Handlebars build (require('handlebars/runtime')) which eliminates the compile() function from the attack surface, implement strict input sanitization to ensure no context property contains non-primitive objects that could be passed to dynamic partials, or refactor templates to eliminate dynamic partial lookups ({{> (lookup ...)}}) when rendering user-controlled data. Review all template implementations for patterns combining lookup helpers with partial invocation syntax.

Vendor StatusVendor

Share

EUVD-2026-16860 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy