VYPR
Critical severityNVD Advisory· Published Dec 20, 2019· Updated Aug 5, 2024

CVE-2019-19919

CVE-2019-19919

Description

Versions of handlebars prior to 4.3.0 are vulnerable to Prototype Pollution leading to Remote Code Execution. Templates may alter an Object's __proto__ and __defineGetter__ properties, which may allow an attacker to execute arbitrary code through crafted payloads.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Handlebars.js before 4.3.0 allows prototype pollution via crafted templates, enabling remote code execution.

The vulnerability is a prototype pollution issue in Handlebars.js, a popular templating engine. Versions prior to 4.3.0 allow templates to manipulate an Object's __proto__ and __defineGetter__ properties, leading to potential remote code execution [1][2].

An attacker can exploit this by providing a crafted template that pollutes the Object prototype through special properties. This can be done by injecting malicious template expressions that modify __proto__ or __defineGetter__ [2]. The attack does not require authentication if the attacker can control the template input.

Successful exploitation allows the attacker to execute arbitrary code in the context of the application using Handlebars. This can lead to full compromise of the affected system, including data theft or further attacks [3].

The vulnerability was fixed in Handlebars.js version 4.3.0 [4]. Users should upgrade immediately. Vendor advisories have been published, and products like Tenable.sc have released patches to address this issue [3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
handlebarsnpm
>= 4.0.0, < 4.3.04.3.0
bootstrap-wysihtml5-railsRubyGems
>= 0.3.3.5, <= 0.3.3.8
handlebarsnpm
< 3.0.83.0.8

Affected products

113

Patches

3
90ad8d97ad29

Update release notes

https://github.com/handlebars-lang/handlebars.jsNils KnappmeierFeb 23, 2020via ghsa
1 file changed · +24 1
  • release-notes.md+24 1 modified
    @@ -2,7 +2,30 @@
     
     ## Development
     
    -[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.7...master)
    +[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.8...master)
    +
    +## v3.0.8 - February 23rd, 2020
    +Bugfixes:
    +- backport some (but not all) of the security fixes from 4.x - 156061e
    +
    +Compatibility notes:
    +- The properties `__proto__`, `__defineGetter__`, `__defineSetter__` and `__lookupGetter__` 
    +  have been added to the list of "dangerous properties". If a property
    +  by that name is found and not an own-property of its parent, it will silently evaluate to undefined.
    +  This is done in both the compiled template and the "lookup"-helper. This will prevent
    +  Remote-Code-Execution exploits that have been published in npm advisories [1324](https://www.npmjs.com/advisories/1324)
    +  and [1316](https://www.npmjs.com/advisories/1316).
    +- The check for dangerous properties has been changed from "propertyIsEnumerable" to "hasOwnProperty", as it is now done 
    +  in Handlebars 4.6.0 and later.
    +
    +Security issues resolved:
    +- [npm advisory 1324](https://www.npmjs.com/advisories/1324)
    +- [npm advisory 1316](https://www.npmjs.com/advisories/1316)
    +- [npm advisory 1325](https://www.npmjs.com/advisories/1325)
    +- [npm advisory 1164](https://www.npmjs.com/advisories/1164)
    +
    +
    +[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.7...v3.0.8)
     
     ## v3.0.7 - June 30th, 2019
     Security fixes:
    
156061eb7707

backport fixes from 4.x

https://github.com/handlebars-lang/handlebars.jsNils KnappmeierFeb 22, 2020via ghsa
4 files changed · +31 8
  • lib/handlebars/base.js+1 1 modified
    @@ -215,7 +215,7 @@ function registerDefaultHelpers(instance) {
         if (!obj) {
           return obj;
         }
    -    if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
    +    if (Utils.dangerousPropertyRegex.test(String(field)) && !Object.prototype.hasOwnProperty.call(obj, field)) {
           return undefined;
         }
         return obj[field];
    
  • lib/handlebars/compiler/javascript-compiler.js+12 7 modified
    @@ -1,6 +1,6 @@
     import { COMPILER_REVISION, REVISION_CHANGES } from '../base';
     import Exception from '../exception';
    -import {isArray} from '../utils';
    +import {isArray, dangerousPropertyRegex} from '../utils';
     import CodeGen from './code-gen';
     
     function Literal(value) {
    @@ -13,13 +13,18 @@ JavaScriptCompiler.prototype = {
       // PUBLIC API: You can override these methods in a subclass to provide
       // alternative compiled forms for name lookup and buffering semantics
       nameLookup: function(parent, name /* , type*/) {
    -    if (name === 'constructor') {
    -      return ['(', parent, '.propertyIsEnumerable(\'constructor\') ? ', parent, '.constructor : undefined', ')'];
    +    if (dangerousPropertyRegex.test(name)) {
    +      const isOwnProperty = [ this.aliasable('Object.prototype.hasOwnProperty'), '.call(', parent, ',', JSON.stringify(name), ')'];
    +      return ['(', isOwnProperty, '?', _actualLookup(), ' : undefined)'];
         }
    -    if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
    -      return [parent, '.', name];
    -    } else {
    -      return [parent, "['", name, "']"];
    +    return _actualLookup();
    +
    +    function _actualLookup() {
    +      if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
    +        return [parent, '.', name];
    +      } else {
    +        return [parent, '[', JSON.stringify(name), ']'];
    +      }
         }
       },
       depthedLookup: function(name) {
    
  • lib/handlebars/utils.js+2 0 modified
    @@ -101,3 +101,5 @@ export function blockParams(params, ids) {
     export function appendContextPath(contextPath, id) {
       return (contextPath ? contextPath + '.' : '') + id;
     }
    +
    +export const dangerousPropertyRegex = /^(constructor|__defineGetter__|__defineSetter__|__lookupGetter__|__proto__)$/;
    
  • spec/security.js+16 0 modified
    @@ -30,4 +30,20 @@ describe('security issues', function() {
                     new TestClass(), 'xyz');
             });
         });
    +
    +    describe('GH-1595', function() {
    +        it('properties, that are required to be enumerable', function() {
    +            shouldCompileTo('{{constructor.name}}', {}, '');
    +            shouldCompileTo('{{__defineGetter__.name}}', {}, '');
    +            shouldCompileTo('{{__defineSetter__.name}}', {}, '');
    +            shouldCompileTo('{{__lookupGetter__.name}}', {}, '');
    +            shouldCompileTo('{{__proto__.__defineGetter__.name}}', {}, '');
    +
    +            shouldCompileTo('{{lookup this "constructor"}}', {}, '');
    +            shouldCompileTo('{{lookup this "__defineGetter__"}}', {}, '');
    +            shouldCompileTo('{{lookup this "__defineSetter__"}}', {}, '');
    +            shouldCompileTo('{{lookup this "__lookupGetter__"}}', {}, '');
    +            shouldCompileTo('{{lookup this "__proto__"}}', {}, '');
    +        });
    +    });
     });
    
2078c727c627

Disallow calling "helperMissing" and "blockHelperMissing" directly

https://github.com/wycats/handlebars.jsNils KnappmeierSep 16, 2019via ghsa
6 files changed · +109 25
  • lib/handlebars/compiler/javascript-compiler.js+23 9 modified
    @@ -311,7 +311,7 @@ JavaScriptCompiler.prototype = {
       // replace it on the stack with the result of properly
       // invoking blockHelperMissing.
       blockValue: function(name) {
    -    let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
    +    let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
             params = [this.contextName(0)];
         this.setupHelperArgs(name, 0, params);
     
    @@ -329,7 +329,7 @@ JavaScriptCompiler.prototype = {
       // On stack, after, if lastHelper: value
       ambiguousBlockValue: function() {
         // We're being a bit cheeky and reusing the options value from the prior exec
    -    let blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
    +    let blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
             params = [this.contextName(0)];
         this.setupHelperArgs('', 0, params, true);
     
    @@ -622,18 +622,32 @@ JavaScriptCompiler.prototype = {
       // If the helper is not found, `helperMissing` is called.
       invokeHelper: function(paramSize, name, isSimple) {
         let nonHelper = this.popStack(),
    -        helper = this.setupHelper(paramSize, name),
    -        simple = isSimple ? [helper.name, ' || '] : '';
    +        helper = this.setupHelper(paramSize, name);
     
    -    let lookup = ['('].concat(simple, nonHelper);
    +    let possibleFunctionCalls = [];
    +
    +    if (isSimple) { // direct call to helper
    +      possibleFunctionCalls.push(helper.name);
    +    }
    +    // call a function from the input object
    +    possibleFunctionCalls.push(nonHelper);
         if (!this.options.strict) {
    -      lookup.push(' || ', this.aliasable('helpers.helperMissing'));
    +      possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing'));
         }
    -    lookup.push(')');
     
    -    this.push(this.source.functionCall(lookup, 'call', helper.callParams));
    +    let functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')'];
    +    let functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams);
    +    this.push(functionCall);
       },
     
    +  itemsSeparatedBy: function(items, separator) {
    +    let result = [];
    +    result.push(items[0]);
    +    for (let i = 1; i < items.length; i++) {
    +      result.push(separator, items[i]);
    +    }
    +    return result;
    +  },
       // [invokeKnownHelper]
       //
       // On stack, before: hash, inverse, program, params..., ...
    @@ -673,7 +687,7 @@ JavaScriptCompiler.prototype = {
           lookup[0] = '(helper = ';
           lookup.push(
             ' != null ? helper : ',
    -        this.aliasable('helpers.helperMissing')
    +        this.aliasable('container.hooks.helperMissing')
           );
         }
     
    
  • lib/handlebars/helpers.js+9 0 modified
    @@ -15,3 +15,12 @@ export function registerDefaultHelpers(instance) {
       registerLookup(instance);
       registerWith(instance);
     }
    +
    +export function moveHelperToHooks(instance, helperName, keepHelper) {
    +  if (instance.helpers[helperName]) {
    +    instance.hooks[helperName] = instance.helpers[helperName];
    +    if (!keepHelper) {
    +      delete instance.helpers[helperName];
    +    }
    +  }
    +}
    
  • lib/handlebars/runtime.js+19 16 modified
    @@ -1,6 +1,7 @@
     import * as Utils from './utils';
     import Exception from './exception';
    -import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
    +import {COMPILER_REVISION, createFrame, REVISION_CHANGES} from './base';
    +import {moveHelperToHooks} from './helpers';
     
     export function checkRevision(compilerInfo) {
       const compilerRevision = compilerInfo && compilerInfo[0] || 1,
    @@ -21,6 +22,7 @@ export function checkRevision(compilerInfo) {
     }
     
     export function template(templateSpec, env) {
    +
       /* istanbul ignore next */
       if (!env) {
         throw new Exception('No environment passed to template');
    @@ -42,13 +44,15 @@ export function template(templateSpec, env) {
             options.ids[0] = true;
           }
         }
    -
         partial = env.VM.resolvePartial.call(this, partial, context, options);
    -    let result = env.VM.invokePartial.call(this, partial, context, options);
    +
    +    let optionsWithHooks = Utils.extend({}, options, {hooks: this.hooks});
    +
    +    let result = env.VM.invokePartial.call(this, partial, context, optionsWithHooks);
     
         if (result == null && env.compile) {
           options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
    -      result = options.partials[options.name](context, options);
    +      result = options.partials[options.name](context, optionsWithHooks);
         }
         if (result != null) {
           if (options.indent) {
    @@ -115,15 +119,6 @@ export function template(templateSpec, env) {
           }
           return value;
         },
    -    merge: function(param, common) {
    -      let obj = param || common;
    -
    -      if (param && common && (param !== common)) {
    -        obj = Utils.extend({}, common, param);
    -      }
    -
    -      return obj;
    -    },
         // An empty object to use as replacement for null-contexts
         nullContext: Object.seal({}),
     
    @@ -158,19 +153,27 @@ export function template(templateSpec, env) {
     
       ret._setup = function(options) {
         if (!options.partial) {
    -      container.helpers = container.merge(options.helpers, env.helpers);
    +      container.helpers = Utils.extend({}, env.helpers, options.helpers);
     
           if (templateSpec.usePartial) {
    -        container.partials = container.merge(options.partials, env.partials);
    +        container.partials = Utils.extend({}, env.partials, options.partials);
           }
           if (templateSpec.usePartial || templateSpec.useDecorators) {
    -        container.decorators = container.merge(options.decorators, env.decorators);
    +        container.decorators = Utils.extend({}, env.decorators, options.decorators);
           }
    +
    +      container.hooks = {};
    +      let keepHelper = options.allowCallsToHelperMissing;
    +      moveHelperToHooks(container, 'helperMissing', keepHelper);
    +      moveHelperToHooks(container, 'blockHelperMissing', keepHelper);
    +
         } else {
           container.helpers = options.helpers;
           container.partials = options.partials;
           container.decorators = options.decorators;
    +      container.hooks = options.hooks;
         }
    +
       };
     
       ret._child = function(i, data, blockParams, depths) {
    
  • lib/handlebars/utils.js+1 0 modified
    @@ -1,3 +1,4 @@
    +
     const escape = {
       '&': '&amp;',
       '<': '&lt;',
    
  • spec/security.js+56 0 modified
    @@ -32,4 +32,60 @@ describe('security issues', function() {
     
             });
         });
    +
    +    describe('GH-xxxx: Prevent explicit call of helperMissing-helpers', function() {
    +        if (!Handlebars.compile) {
    +            return;
    +        }
    +
    +        describe('without the option "allowExplicitCallOfHelperMissing"', function() {
    +            it('should throw an exception when calling  "{{helperMissing}}" ', function() {
    +                shouldThrow(function() {
    +                    var template = Handlebars.compile('{{helperMissing}}');
    +                    template({});
    +                }, Error);
    +            });
    +            it('should throw an exception when calling  "{{#helperMissing}}{{/helperMissing}}" ', function() {
    +                shouldThrow(function() {
    +                    var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
    +                    template({});
    +                }, Error);
    +            });
    +            it('should throw an exception when calling  "{{blockHelperMissing "abc" .}}" ', function() {
    +                var functionCalls = [];
    +                shouldThrow(function() {
    +                    var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
    +                    template({ fn: function() { functionCalls.push('called'); }});
    +                }, Error);
    +                equals(functionCalls.length, 0);
    +            });
    +            it('should throw an exception when calling  "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
    +                shouldThrow(function() {
    +                    var template = Handlebars.compile('{{#blockHelperMissing .}}{{/blockHelperMissing}}');
    +                    template({ fn: function() { return 'functionInData';}});
    +                }, Error);
    +            });
    +        });
    +
    +        describe('with the option "allowCallsToHelperMissing" set to true', function() {
    +            it('should not throw an exception when calling  "{{helperMissing}}" ', function() {
    +                    var template = Handlebars.compile('{{helperMissing}}');
    +                    template({}, {allowCallsToHelperMissing: true});
    +            });
    +            it('should not throw an exception when calling  "{{#helperMissing}}{{/helperMissing}}" ', function() {
    +                    var template = Handlebars.compile('{{#helperMissing}}{{/helperMissing}}');
    +                    template({}, {allowCallsToHelperMissing: true});
    +            });
    +            it('should not throw an exception when calling  "{{blockHelperMissing "abc" .}}" ', function() {
    +                    var functionCalls = [];
    +                    var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
    +                    template({ fn: function() { functionCalls.push('called'); }}, {allowCallsToHelperMissing: true});
    +                    equals(functionCalls.length, 1);
    +            });
    +            it('should not throw an exception when calling  "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
    +                    var template = Handlebars.compile('{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}');
    +                    template({}, {allowCallsToHelperMissing: true});
    +            });
    +        });
    +    });
     });
    
  • types/index.d.ts+1 0 modified
    @@ -29,6 +29,7 @@ declare namespace Handlebars {
           decorators?: { [name: string]: Function };
           data?: any;
           blockParams?: any[];
    +      allowCallsToHelperMissing: boolean;
       }
     
       export interface HelperOptions {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

12

News mentions

0

No linked articles in our index yet.