CVE-2019-20920
Description
Handlebars before 3.0.8 and 4.x before 4.5.3 is vulnerable to Arbitrary Code Execution. The lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript. This can be used to run arbitrary code on a server processing Handlebars templates or in a victim's browser (effectively serving as XSS).
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Handlebars before 3.0.8 and 4.x before 4.5.3 allows arbitrary code execution via the lookup helper, enabling server-side RCE or XSS.
Vulnerability
Overview Handlebars, a popular JavaScript templating engine, is vulnerable to arbitrary code execution (ACE) in versions prior to 3.0.8 and 4.x prior to 4.5.3. The flaw resides in the lookup helper, which fails to properly validate property names when accessing object properties. Specifically, the helper does not block access to the constructor property or other dangerous built-in properties, allowing an attacker to traverse the prototype chain and execute arbitrary JavaScript [1][2][4].
Exploitation
Details An attacker can submit a crafted Handlebars template that leverages the lookup helper to access constructor, toString, or other prototype properties. By constructing a chain like {{lookup (lookup this "constructor") "name"}} or using similar techniques, the attacker can invoke arbitrary functions. The proof-of-concept (PoC) provided in the Snyk advisory demonstrates how to execute alert('Vulnerable Handlebars JS'); in the context of the application [2][4]. This can be exploited in two primary scenarios: on the server side, where templates are processed by Node.js, leading to remote code execution (RCE); or in the browser, where the template output is rendered, effectively serving as stored or reflected cross-site scripting (XSS) [1].
Impact
Successful exploitation allows an attacker to execute arbitrary JavaScript with the privileges of the application. On a server, this can lead to full system compromise, data exfiltration, or lateral movement. In a client-side context, the attacker can steal session cookies, deface web pages, or perform actions on behalf of the victim user [1][4].
Mitigation
The vulnerability is fixed in Handlebars versions 3.0.8 and 4.5.3 and later. Users should upgrade their handlebars dependency immediately. The fix introduces a dangerousPropertyRegex to filter out access to dangerous properties like constructor and __proto__, and modifies the nameLookup function in the compiler to check for own properties before allowing access [3]. No workarounds are documented; upgrading is the recommended action [4].
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.
| Package | Affected versions | Patched versions |
|---|---|---|
handlebarsnpm | < 3.0.8 | 3.0.8 |
handlebarsnpm | >= 4.0.0, < 4.5.3 | 4.5.3 |
Affected products
114- Handlebars/Handlebarsdescription
- osv-coords113 versionspkg:apk/chainguard/code-serverpkg:apk/chainguard/code-server-compatpkg:apk/chainguard/gitlab-rails-ce-18.1pkg:apk/chainguard/gitlab-rails-ce-18.2pkg:apk/chainguard/gitlab-rails-ce-18.3pkg:apk/chainguard/gitlab-rails-ce-18.4pkg:apk/chainguard/gitlab-rails-ce-18.5pkg:apk/chainguard/gitlab-rails-ce-18.6pkg:apk/chainguard/gitlab-rails-ce-18.7pkg:apk/chainguard/gitlab-rails-ce-assets-18.1pkg:apk/chainguard/gitlab-rails-ce-assets-18.10pkg:apk/chainguard/gitlab-rails-ce-assets-18.11pkg:apk/chainguard/gitlab-rails-ce-assets-18.2pkg:apk/chainguard/gitlab-rails-ce-assets-18.3pkg:apk/chainguard/gitlab-rails-ce-assets-18.4pkg:apk/chainguard/gitlab-rails-ce-assets-18.5pkg:apk/chainguard/gitlab-rails-ce-assets-18.6pkg:apk/chainguard/gitlab-rails-ce-assets-18.7pkg:apk/chainguard/gitlab-rails-ce-assets-18.8pkg:apk/chainguard/gitlab-rails-ce-assets-18.9pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.1pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.10pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.11pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.2pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.3pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.4pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.5pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.6pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.7pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.8pkg:apk/chainguard/gitlab-rails-ce-assets-fips-18.9pkg:apk/chainguard/gitlab-rails-ce-doc-18.1pkg:apk/chainguard/gitlab-rails-ce-doc-18.2pkg:apk/chainguard/gitlab-rails-ce-doc-18.3pkg:apk/chainguard/gitlab-rails-ce-doc-18.4pkg:apk/chainguard/gitlab-rails-ce-doc-18.5pkg:apk/chainguard/gitlab-rails-ce-doc-18.6pkg:apk/chainguard/gitlab-rails-ce-doc-18.7pkg:apk/chainguard/gitlab-rails-ce-doc-fips-18.1pkg:apk/chainguard/gitlab-rails-ce-doc-fips-18.2pkg:apk/chainguard/gitlab-rails-ce-doc-fips-18.3pkg:apk/chainguard/gitlab-rails-ce-doc-fips-18.4pkg:apk/chainguard/gitlab-rails-ce-doc-fips-18.5pkg:apk/chainguard/gitlab-rails-ce-doc-fips-18.6pkg:apk/chainguard/gitlab-rails-ce-doc-fips-18.7pkg:apk/chainguard/gitlab-rails-ce-fips-18.1pkg:apk/chainguard/gitlab-rails-ce-fips-18.2pkg:apk/chainguard/gitlab-rails-ce-fips-18.3pkg:apk/chainguard/gitlab-rails-ce-fips-18.4pkg:apk/chainguard/gitlab-rails-ce-fips-18.5pkg:apk/chainguard/gitlab-rails-ce-fips-18.6pkg:apk/chainguard/gitlab-rails-ce-fips-18.7pkg:apk/chainguard/gitlab-rails-ee-17.0pkg:apk/chainguard/gitlab-rails-ee-17.1pkg:apk/chainguard/gitlab-rails-ee-17.10pkg:apk/chainguard/gitlab-rails-ee-17.11pkg:apk/chainguard/gitlab-rails-ee-17.2pkg:apk/chainguard/gitlab-rails-ee-17.3pkg:apk/chainguard/gitlab-rails-ee-17.4pkg:apk/chainguard/gitlab-rails-ee-17.6pkg:apk/chainguard/gitlab-rails-ee-17.7pkg:apk/chainguard/gitlab-rails-ee-17.8pkg:apk/chainguard/gitlab-rails-ee-17.9pkg:apk/chainguard/gitlab-rails-ee-assets-17.10pkg:apk/chainguard/gitlab-rails-ee-assets-17.11pkg:apk/chainguard/gitlab-rails-ee-assets-17.2pkg:apk/chainguard/gitlab-rails-ee-assets-17.3pkg:apk/chainguard/gitlab-rails-ee-assets-17.4pkg:apk/chainguard/gitlab-rails-ee-assets-17.6pkg:apk/chainguard/gitlab-rails-ee-assets-17.7pkg:apk/chainguard/gitlab-rails-ee-assets-17.8pkg:apk/chainguard/gitlab-rails-ee-assets-17.9pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.10pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.11pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.2pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.3pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.4pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.6pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.7pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.8pkg:apk/chainguard/gitlab-rails-ee-assets-fips-17.9pkg:apk/chainguard/gitlab-rails-ee-doc-17.10pkg:apk/chainguard/gitlab-rails-ee-doc-17.11pkg:apk/chainguard/gitlab-rails-ee-doc-17.2pkg:apk/chainguard/gitlab-rails-ee-doc-17.3pkg:apk/chainguard/gitlab-rails-ee-doc-17.4pkg:apk/chainguard/gitlab-rails-ee-doc-17.6pkg:apk/chainguard/gitlab-rails-ee-doc-17.7pkg:apk/chainguard/gitlab-rails-ee-doc-17.8pkg:apk/chainguard/gitlab-rails-ee-doc-17.9pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.10pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.11pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.2pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.3pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.4pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.6pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.7pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.8pkg:apk/chainguard/gitlab-rails-ee-doc-fips-17.9pkg:apk/chainguard/gitlab-rails-ee-fips-17.0pkg:apk/chainguard/gitlab-rails-ee-fips-17.1pkg:apk/chainguard/gitlab-rails-ee-fips-17.10pkg:apk/chainguard/gitlab-rails-ee-fips-17.11pkg:apk/chainguard/gitlab-rails-ee-fips-17.2pkg:apk/chainguard/gitlab-rails-ee-fips-17.3pkg:apk/chainguard/gitlab-rails-ee-fips-17.4pkg:apk/chainguard/gitlab-rails-ee-fips-17.6pkg:apk/chainguard/gitlab-rails-ee-fips-17.7pkg:apk/chainguard/gitlab-rails-ee-fips-17.8pkg:apk/chainguard/gitlab-rails-ee-fips-17.9pkg:apk/wolfi/code-serverpkg:apk/wolfi/code-server-compatpkg:npm/handlebars
< 0+ 112 more
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 3.0.8
Patches
416bd606fec0av3.0.8
4 files changed · +4 −4
components/bower.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "3.0.7", + "version": "3.0.8", "main": "handlebars.js", "dependencies": {} }
components/handlebars.js.nuspec+1 −1 modified@@ -2,7 +2,7 @@ <package> <metadata> <id>handlebars.js</id> - <version>3.0.7</version> + <version>3.0.8</version> <authors>handlebars.js Authors</authors> <licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl> <projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
components/package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "3.0.7", + "version": "3.0.8", "license": "MIT", "jspm": { "main": "handlebars",
package.json+1 −1 modified@@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "3.0.7", + "version": "3.0.8", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [
c819c8b53393v4.5.3
5 files changed · +5 −5
components/bower.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.5.2", + "version": "4.5.3", "main": "handlebars.js", "license": "MIT", "dependencies": {}
components/handlebars.js.nuspec+1 −1 modified@@ -2,7 +2,7 @@ <package> <metadata> <id>handlebars.js</id> - <version>4.5.2</version> + <version>4.5.3</version> <authors>handlebars.js Authors</authors> <licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl> <projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
components/package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "handlebars", - "version": "4.5.2", + "version": "4.5.3", "license": "MIT", "jspm": { "main": "handlebars",
lib/handlebars/base.js+1 −1 modified@@ -4,7 +4,7 @@ import {registerDefaultHelpers} from './helpers'; import {registerDefaultDecorators} from './decorators'; import logger from './logger'; -export const VERSION = '4.5.2'; +export const VERSION = '4.5.3'; export const COMPILER_REVISION = 8; export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
package.json+1 −1 modified@@ -1,7 +1,7 @@ { "name": "handlebars", "barename": "handlebars", - "version": "4.5.2", + "version": "4.5.3", "description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration", "homepage": "http://www.handlebarsjs.com/", "keywords": [
156061eb7707backport fixes from 4.x
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__"}}', {}, ''); + }); + }); });
d54137810a49fix: use String(field) in lookup when checking for "constructor"
2 files changed · +26 −4
lib/handlebars/helpers/lookup.js+1 −1 modified@@ -3,7 +3,7 @@ export default function(instance) { if (!obj) { return obj; } - if (field === 'constructor' && !obj.propertyIsEnumerable(field)) { + if (String(field) === 'constructor' && !obj.propertyIsEnumerable(field)) { return undefined; } return obj[field];
spec/security.js+25 −3 modified@@ -1,11 +1,26 @@ describe('security issues', function() { describe('GH-1495: Prevent Remote Code Execution via constructor', function() { it('should not allow constructors to be accessed', function() { - shouldCompileTo('{{constructor.name}}', {}, ''); - shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {}, ''); + expectTemplate('{{lookup (lookup this "constructor") "name"}}') + .withInput({}) + .toCompileTo(''); + + expectTemplate('{{constructor.name}}') + .withInput({}) + .toCompileTo(''); }); - it('should allow the "constructor" property to be accessed if it is enumerable', function() { + it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() { + expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}') + .withInput({}) + .withHelper('list', function(element) { + return [element]; + }) + .toCompileTo(''); + }); + + + it('should allow the "constructor" property to be accessed if it is enumerable', function() { shouldCompileTo('{{constructor.name}}', {'constructor': { 'name': 'here we go' }}, 'here we go'); @@ -14,6 +29,13 @@ describe('security issues', function() { }}, 'here we go'); }); + it('should allow the "constructor" property to be accessed if it is enumerable', function() { + shouldCompileTo('{{lookup (lookup this "constructor") "name"}}', {'constructor': { + 'name': 'here we go' + }}, 'here we go'); + }); + + it('should allow prototype properties that are not constructors', function() { function TestClass() { }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-3cqr-58rm-57f8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-20920ghsaADVISORY
- github.com/handlebars-lang/handlebars.js/commit/156061eb7707575293613d7fdf90e2bdaac029eeghsaWEB
- github.com/handlebars-lang/handlebars.js/commit/d54137810a49939fd2ad01a91a34e182ece4528eghsaWEB
- snyk.io/vuln/SNYK-JS-HANDLEBARS-534478ghsax_refsource_MISCWEB
- www.npmjs.com/advisories/1316ghsax_refsource_MISCWEB
- www.npmjs.com/advisories/1324ghsax_refsource_MISCWEB
- www.npmjs.com/package/handlebarsghsaWEB
News mentions
0No linked articles in our index yet.