{"id":"CVE-2026-33916","aliases":["CLSA-2026-1782349135","CLSA-2026-1782435546","CLSA-2026-1785117463","CLSA-2026-1792139237","DEBIAN-CVE-2026-33916","EUVD-2026-16847","GHSA-2qvq-rjwj-gvw9","GHSA-442j-39wm-28r2","GHSA-7rx3-28cr-v5wh","ROOT-APP-NPM-CVE-2026-33916","ROOT-APP-NPM-GHSA-7rx3-28cr-v5wh","UBUNTU-CVE-2026-33916"],"status":"published","title":"Handlebars.js has Prototype Pollution Leading to XSS through Partial Template Injection","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, `resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS. Version 4.7.9 fixes the issue. Some workarounds are available. Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries, and/or use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","severity":{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.0028,"percentile":0.20678,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-1321","CWE-367","CWE-79"],"published":"2026-03-26T22:20:51Z","modified":"2026-10-28T02:30:51Z","fixes":[{"ecosystem":"npm","package":"handlebars","fixed_in":["4.7.9"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"node-handlebars","fixed_in":["3:4.7.9-1"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"handlebars","fixed_in":["4.7.8-aikido.6","4.7.7-aikido.3","4.7.7-aikido.4","4.7.7-aikido.5","4.7.9-aikido.1","4.7.9-aikido.2"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"@rootio/handlebars","fixed_in":["4.7.8-root.io.3","4.7.8-root.io.4","4.7.8-root.io.5","4.7.8-root.io.6","4.7.7-root.io.3","4.7.7-root.io.4","4.7.7-root.io.5","4.7.9-root.io.1","4.7.9-root.io.2"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"fixed":"4.7.9"}]}]},{"source":"ghsa","ecosystem":"npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"last_affected":"4.7.8"},{"fixed":"4.7.9"}]}]},{"source":"ghsa","ecosystem":"npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.6.0"},{"last_affected":"4.7.8"},{"fixed":"4.7.9"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.7.7-tuxcare.0"},{"fixed":"4.7.7-tuxcare.1"}]}],"versions":["4.7.7"],"bundled":6},{"source":"osv","ecosystem":"TuxCare:npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.7.8-tuxcare.0"},{"fixed":"4.7.8-tuxcare.1"}]}],"versions":["4.7.8"],"bundled":6},{"source":"osv","ecosystem":"TuxCare:npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.0.8-tuxcare.0"},{"fixed":"3.0.8-tuxcare.3"}]}],"versions":["3.0.8","3.0.8-tuxcare.1","3.0.8-tuxcare.2"],"bundled":4},{"source":"osv","ecosystem":"TuxCare:npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.5.3-tuxcare.0"},{"fixed":"4.5.3-tuxcare.3"}]}],"versions":["4.5.3","4.5.3-tuxcare.1","4.5.3-tuxcare.2"],"bundled":6},{"source":"osv","ecosystem":"Debian:12","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.7.10-1","3:4.7.7+~4.1.0-1","3:4.7.9-1","3:4.7.9-2","3:4.7.9-3","3:4.7.9-4","3:4.7.9-5","3:4.7.9-6"]},{"source":"osv","ecosystem":"Debian:13","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.7.10-1","3:4.7.7+~4.1.0-1","3:4.7.9-1","3:4.7.9-2","3:4.7.9-3","3:4.7.9-4","3:4.7.9-5","3:4.7.9-6"]},{"source":"osv","ecosystem":"Debian:14","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3:4.7.9-1"}]}],"versions":["3:4.7.7+~4.1.0-1"]},{"source":"osv","ecosystem":"npm","package":"handlebars","ranges":[{"type":"SEMVER","events":[{"introduced":"4.0.0"},{"fixed":"4.7.9"}]}]},{"source":"osv","ecosystem":"npm","package":"handlebars","ranges":[{"type":"SEMVER","events":[{"introduced":"4.0.0"},{"fixed":"4.7.9"}]}]},{"source":"osv","ecosystem":"npm","package":"handlebars","ranges":[{"type":"SEMVER","events":[{"introduced":"4.6.0"},{"fixed":"4.7.9"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.7.8-aikido.6"},{"fixed":"4.7.7-aikido.3"},{"fixed":"4.7.7-aikido.4"},{"fixed":"4.7.7-aikido.5"},{"fixed":"4.7.9-aikido.1"},{"fixed":"4.7.9-aikido.2"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"@rootio/handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.7.8-root.io.3"},{"fixed":"4.7.8-root.io.4"},{"fixed":"4.7.8-root.io.5"},{"fixed":"4.7.8-root.io.6"},{"fixed":"4.7.7-root.io.3"},{"fixed":"4.7.7-root.io.4"},{"fixed":"4.7.7-root.io.5"},{"fixed":"4.7.9-root.io.1"},{"fixed":"4.7.9-root.io.2"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.7.8-aikido.6"},{"fixed":"4.7.7-aikido.3"},{"fixed":"4.7.7-aikido.4"},{"fixed":"4.7.7-aikido.5"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"@rootio/handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.7.8-root.io.5"},{"fixed":"4.7.8-root.io.6"},{"fixed":"4.7.7-root.io.3"},{"fixed":"4.7.7-root.io.4"},{"fixed":"4.7.7-root.io.5"}]}]},{"source":"osv","ecosystem":"Ubuntu:18.04:LTS","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.0.10-5"]},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.1.0-1","3:4.5.3-1","3:4.7.2-1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.7.6+~4.1.0-2","3:4.7.7+~4.1.0-1"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.7.7+~4.1.0-1"]},{"source":"osv","ecosystem":"Ubuntu:25.10","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.7.7+~4.1.0-1"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.7.7+~4.1.0-1","3:4.7.9-5"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"node-handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3:4.7.7+~4.1.0-1"]}],"products":[{"vendor":"handlebars-lang","product":"handlebars.js"},{"vendor":"handlebarsjs","product":"handlebars"}],"references":[{"url":"https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-2qvq-rjwj-gvw9","tags":["x_refsource_CONFIRM","Exploit","Vendor Advisory","advisory","web","report"]},{"url":"https://github.com/handlebars-lang/handlebars.js/commit/68d8df5a88e0a26fe9e6084c5c6aaebe67b07da2","tags":["x_refsource_MISC","Patch","fix","web","report"]},{"url":"https://github.com/handlebars-lang/handlebars.js/releases/tag/v4.7.9","tags":["x_refsource_MISC","Release Notes","web","report"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2021-23369","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2021-23383","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33916","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-2qvq-rjwj-gvw9"},{"url":"https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-442j-39wm-28r2","tags":["web"]},{"url":"https://github.com/advisories/GHSA-442j-39wm-28r2"},{"url":"https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-7rx3-28cr-v5wh","tags":["web"]},{"url":"https://github.com/advisories/GHSA-765h-qjxv-5f44","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-7rx3-28cr-v5wh"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/33xxx/CVE-2026-33916.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-33916","tags":["advisory"]},{"url":"https://github.com/handlebars-lang/handlebars.js","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-33916","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-33916","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-33916","title":"Handlebars.js has Prototype Pollution Leading to XSS through Partial Template Injection","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, `resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS. Version 4.7.9 fixes the issue. Some workarounds are available. Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries, and/or use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","status":"published","published":"2026-03-27T21:00:48.624Z","modified":"2026-03-30T15:41:36.977Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-79","CWE-1321"],"updated_at":"2026-11-02T14:20:08.009733Z"}],"euvd":[{"id":"EUVD-2026-16847","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, `resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS. Version 4.7.9 fixes the issue. Some workarounds are available. Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries, and/or use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","status":"published","published":"2026-03-27T21:00:48Z","modified":"2026-03-30T15:41:36Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T19:25:25.878486Z"}],"ghsa":[{"id":"GHSA-2qvq-rjwj-gvw9","title":"Handlebars.js has Prototype Pollution Leading to XSS through Partial Template Injection","description":"## Summary\n\n`resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered **without HTML escaping**, resulting in reflected or stored XSS.\n\n## Description\n\nThe root cause is in `lib/handlebars/runtime.js` inside `resolvePartial()` and `invokePartial()`:\n\n```javascript\n// Vulnerable: plain bracket access traverses Object.prototype\npartial = options.partials[options.name];\n```\n\n`hasOwnProperty` is never checked, so if `Object.prototype` has been seeded with a key whose name matches a partial reference in the template (e.g. `widget`), the lookup succeeds and the polluted string is returned. The runtime emits a prototype-access warning, but the partial is still resolved and its content is inserted into the rendered output unescaped. This contradicts the documented security model and is distinct from CVE-2021-23369 and CVE-2021-23383, which addressed data property access rather than partial template resolution.\n\n**Prerequisites for exploitation:**\n1. The target application must be vulnerable to prototype pollution (e.g. via `qs`, `minimist`, or\n   any querystring/JSON merge sink).\n2. The attacker must know or guess the name of a partial reference used in a template.\n\n## Proof of Concept\n\n```javascript\nconst Handlebars = require('handlebars');\n\n// Step 1: Prototype pollution (via qs, minimist, or another vector)\nObject.prototype.widget = '\u003cimg src=x onerror=\"alert(document.domain)\"\u003e';\n\n// Step 2: Normal template that references a partial\nconst template = Handlebars.compile('\u003cdiv\u003eWelcome! {{\u003e widget}}\u003c/div\u003e');\n\n// Step 3: Render — XSS payload injected unescaped\nconst output = template({});\n// Output: \u003cdiv\u003eWelcome! \u003cimg src=x onerror=\"alert(document.domain)\"\u003e\u003c/div\u003e\n```\n\n\u003e The runtime prints a prototype access warning claiming \"access has been denied,\" but the partial still resolves and returns the polluted value.\n\n## Workarounds\n\n- Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries.\n- Use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","status":"published","published":"2026-03-26T22:20:51Z","modified":"2026-03-27T21:52:04Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79","CWE-1321"],"updated_at":"2026-11-02T14:39:44.97342Z"},{"id":"GHSA-442j-39wm-28r2","title":"Handlebars.js has a Property Access Validation Bypass in container.lookup","description":"## Summary\n\nIn `lib/handlebars/runtime.js`, the `container.lookup()` function uses `container.lookupProperty()` as a gate check to enforce prototype-access controls, but then discards the validated result and performs a second, unguarded property access (`depths[i][name]`). This Time-of-Check Time-of-Use (TOCTOU) pattern means the security check and the actual read are decoupled, and the raw access bypasses any sanitization that `lookupProperty` may perform.\n\nOnly relevant when the **compat** compile option is enabled (`{compat: true}`), which activates `depthedLookup` in `lib/handlebars/compiler/javascript-compiler.js`.\n\n## Description\n\nThe vulnerable code in `lib/handlebars/runtime.js` (lines 137–144):\n\n```javascript\nlookup: function (depths, name) {\n  const len = depths.length;\n  for (let i = 0; i \u003c len; i++) {\n    let result = depths[i] \u0026\u0026 container.lookupProperty(depths[i], name);\n    if (result != null) {\n      return depths[i][name];  // BUG: should be `return result;`\n    }\n  }\n},\n```\n\n`container.lookupProperty()` (lines 119–136) enforces `hasOwnProperty` checks and `resultIsAllowed()` prototype-access controls. However, `container.lookup()` only uses `lookupProperty` as a boolean gate — if the gate passes (`result != null`), it then performs an independent, raw `depths[i][name]` access that circumvents any transformation or wrapped value that `lookupProperty` may have returned.\n\n## Workarounds\n\n- Avoid enabling `{ compat: true }` when rendering templates that include untrusted data.\n- Ensure context data objects are plain JSON (no Proxies, no getter-based accessor properties).","status":"published","published":"2026-03-29T15:16:37Z","modified":"2026-03-29T15:16:37Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"low"}],"cwes":["CWE-367"],"updated_at":"2026-11-02T14:39:44.97342Z"},{"id":"GHSA-7rx3-28cr-v5wh","title":"Handlebars.js has a Prototype Method Access Control Gap via Missing __lookupSetter__ Blocklist Entry","description":"## Summary\n\nThe prototype method blocklist in `lib/handlebars/internal/proto-access.js` blocks `constructor`, `__defineGetter__`, `__defineSetter__`, and `__lookupGetter__`, but omits the symmetric `__lookupSetter__`. This omission is only exploitable when the non-default runtime option `allowProtoMethodsByDefault: true` is explicitly set — in that configuration `__lookupSetter__` becomes accessible while its counterparts remain blocked, creating an inconsistent security boundary.\n\n`4.6.0` is the version that introduced `protoAccessControl` and the `allowProtoMethodsByDefault` runtime option.\n\n## Description\n\nIn `lib/handlebars/internal/proto-access.js`:\n\n```javascript\nconst methodWhiteList = Object.create(null);\nmethodWhiteList['constructor']      = false;\nmethodWhiteList['__defineGetter__'] = false;\nmethodWhiteList['__defineSetter__'] = false;\nmethodWhiteList['__lookupGetter__'] = false;\n// __lookupSetter__ intentionally blocked in CVE-2021-23383,\n// but omitted here — creating an asymmetric blocklist\n```\n\nAll four legacy accessor helpers (`__defineGetter__`, `__defineSetter__`, `__lookupGetter__`, `__lookupSetter__`) were involved in the exploit chain addressed by CVE-2021-23383. Three of the four were explicitly blocked; `__lookupSetter__` was left out.\n\nWhen `allowProtoMethodsByDefault: true` is set, any prototype method **not present** in `methodWhiteList` is permitted by default. Because `__lookupSetter__` is absent from the list, it passes the `checkWhiteList` check and is accessible in templates, while `__lookupGetter__` (its sibling) is correctly denied.\n\n## Workarounds\n\n- Do **not** set `allowProtoMethodsByDefault: true`. The default configuration is not affected.\n- If `allowProtoMethodsByDefault` must be enabled, ensure templates do not reference  `__lookupSetter__` through untrusted input.","status":"published","published":"2026-03-29T15:17:15Z","modified":"2026-03-29T15:17:16Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-1321"],"updated_at":"2026-11-02T14:39:44.97342Z"}],"nvd":[{"id":"CVE-2026-33916","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, `resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS. Version 4.7.9 fixes the issue. Some workarounds are available. Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries, and/or use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","status":"published","published":"2026-03-27T21:17:27.237Z","modified":"2026-10-26T22:10:00.163Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"secondary"},{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"primary"}],"cwes":["CWE-79","CWE-1321"],"updated_at":"2026-11-02T14:12:11.678711Z"}],"osv":[{"id":"CVE-2026-33916","title":"Handlebars.js has Prototype Pollution Leading to XSS through Partial Template Injection","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, `resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS. Version 4.7.9 fixes the issue. Some workarounds are available. Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries, and/or use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","status":"published","published":"2026-03-27T21:00:48.624Z","modified":"2026-10-28T02:30:51.851454321Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-1321","CWE-79"],"updated_at":"2026-11-02T15:45:01.896044Z"},{"id":"DEBIAN-CVE-2026-33916","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, `resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS. Version 4.7.9 fixes the issue. Some workarounds are available. Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries, and/or use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","status":"published","published":"2026-03-27T21:17:27.237Z","modified":"2026-10-27T10:00:58.634224602Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:45:54.587575Z"},{"id":"GHSA-2qvq-rjwj-gvw9","title":"Handlebars.js has Prototype Pollution Leading to XSS through Partial Template Injection","description":"## Summary\n\n`resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered **without HTML escaping**, resulting in reflected or stored XSS.\n\n## Description\n\nThe root cause is in `lib/handlebars/runtime.js` inside `resolvePartial()` and `invokePartial()`:\n\n```javascript\n// Vulnerable: plain bracket access traverses Object.prototype\npartial = options.partials[options.name];\n```\n\n`hasOwnProperty` is never checked, so if `Object.prototype` has been seeded with a key whose name matches a partial reference in the template (e.g. `widget`), the lookup succeeds and the polluted string is returned. The runtime emits a prototype-access warning, but the partial is still resolved and its content is inserted into the rendered output unescaped. This contradicts the documented security model and is distinct from CVE-2021-23369 and CVE-2021-23383, which addressed data property access rather than partial template resolution.\n\n**Prerequisites for exploitation:**\n1. The target application must be vulnerable to prototype pollution (e.g. via `qs`, `minimist`, or\n   any querystring/JSON merge sink).\n2. The attacker must know or guess the name of a partial reference used in a template.\n\n## Proof of Concept\n\n```javascript\nconst Handlebars = require('handlebars');\n\n// Step 1: Prototype pollution (via qs, minimist, or another vector)\nObject.prototype.widget = '\u003cimg src=x onerror=\"alert(document.domain)\"\u003e';\n\n// Step 2: Normal template that references a partial\nconst template = Handlebars.compile('\u003cdiv\u003eWelcome! {{\u003e widget}}\u003c/div\u003e');\n\n// Step 3: Render — XSS payload injected unescaped\nconst output = template({});\n// Output: \u003cdiv\u003eWelcome! \u003cimg src=x onerror=\"alert(document.domain)\"\u003e\u003c/div\u003e\n```\n\n\u003e The runtime prints a prototype access warning claiming \"access has been denied,\" but the partial still resolves and returns the polluted value.\n\n## Workarounds\n\n- Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries.\n- Use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","status":"published","published":"2026-03-26T22:20:51Z","modified":"2026-09-10T03:50:36.381674695Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-1321","CWE-79"],"updated_at":"2026-11-02T15:46:34.700741Z"},{"id":"GHSA-442j-39wm-28r2","title":"Handlebars.js has a Property Access Validation Bypass in container.lookup","description":"## Summary\n\nIn `lib/handlebars/runtime.js`, the `container.lookup()` function uses `container.lookupProperty()` as a gate check to enforce prototype-access controls, but then discards the validated result and performs a second, unguarded property access (`depths[i][name]`). This Time-of-Check Time-of-Use (TOCTOU) pattern means the security check and the actual read are decoupled, and the raw access bypasses any sanitization that `lookupProperty` may perform.\n\nOnly relevant when the **compat** compile option is enabled (`{compat: true}`), which activates `depthedLookup` in `lib/handlebars/compiler/javascript-compiler.js`.\n\n## Description\n\nThe vulnerable code in `lib/handlebars/runtime.js` (lines 137–144):\n\n```javascript\nlookup: function (depths, name) {\n  const len = depths.length;\n  for (let i = 0; i \u003c len; i++) {\n    let result = depths[i] \u0026\u0026 container.lookupProperty(depths[i], name);\n    if (result != null) {\n      return depths[i][name];  // BUG: should be `return result;`\n    }\n  }\n},\n```\n\n`container.lookupProperty()` (lines 119–136) enforces `hasOwnProperty` checks and `resultIsAllowed()` prototype-access controls. However, `container.lookup()` only uses `lookupProperty` as a boolean gate — if the gate passes (`result != null`), it then performs an independent, raw `depths[i][name]` access that circumvents any transformation or wrapped value that `lookupProperty` may have returned.\n\n## Workarounds\n\n- Avoid enabling `{ compat: true }` when rendering templates that include untrusted data.\n- Ensure context data objects are plain JSON (no Proxies, no getter-based accessor properties).","status":"published","published":"2026-03-29T15:16:37Z","modified":"2026-09-10T03:50:36.874716263Z","cvss":[{"version":"3.1","score":3.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N","level":"low"}],"cwes":["CWE-367"],"updated_at":"2026-11-02T15:46:36.434231Z"},{"id":"GHSA-7rx3-28cr-v5wh","title":"Handlebars.js has a Prototype Method Access Control Gap via Missing __lookupSetter__ Blocklist Entry","description":"## Summary\n\nThe prototype method blocklist in `lib/handlebars/internal/proto-access.js` blocks `constructor`, `__defineGetter__`, `__defineSetter__`, and `__lookupGetter__`, but omits the symmetric `__lookupSetter__`. This omission is only exploitable when the non-default runtime option `allowProtoMethodsByDefault: true` is explicitly set — in that configuration `__lookupSetter__` becomes accessible while its counterparts remain blocked, creating an inconsistent security boundary.\n\n`4.6.0` is the version that introduced `protoAccessControl` and the `allowProtoMethodsByDefault` runtime option.\n\n## Description\n\nIn `lib/handlebars/internal/proto-access.js`:\n\n```javascript\nconst methodWhiteList = Object.create(null);\nmethodWhiteList['constructor']      = false;\nmethodWhiteList['__defineGetter__'] = false;\nmethodWhiteList['__defineSetter__'] = false;\nmethodWhiteList['__lookupGetter__'] = false;\n// __lookupSetter__ intentionally blocked in CVE-2021-23383,\n// but omitted here — creating an asymmetric blocklist\n```\n\nAll four legacy accessor helpers (`__defineGetter__`, `__defineSetter__`, `__lookupGetter__`, `__lookupSetter__`) were involved in the exploit chain addressed by CVE-2021-23383. Three of the four were explicitly blocked; `__lookupSetter__` was left out.\n\nWhen `allowProtoMethodsByDefault: true` is set, any prototype method **not present** in `methodWhiteList` is permitted by default. Because `__lookupSetter__` is absent from the list, it passes the `checkWhiteList` check and is accessible in templates, while `__lookupGetter__` (its sibling) is correctly denied.\n\n## Workarounds\n\n- Do **not** set `allowProtoMethodsByDefault: true`. The default configuration is not affected.\n- If `allowProtoMethodsByDefault` must be enabled, ensure templates do not reference  `__lookupSetter__` through untrusted input.","status":"published","published":"2026-03-29T15:17:15Z","modified":"2026-09-10T03:50:38.084132596Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-1321"],"updated_at":"2026-11-02T15:46:41.349637Z"},{"id":"ROOT-APP-NPM-CVE-2026-33916","title":"CVE-2026-33916 in handlebars - Patched by Root","description":"Root has patched CVE-2026-33916 in the handlebars package for Root:npm. Multiple fixed versions available.","status":"published","published":"2026-10-15T16:58:53Z","modified":"2026-10-15T17:15:03.217848461Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:53:27.649662Z"},{"id":"ROOT-APP-NPM-GHSA-7rx3-28cr-v5wh","title":"GHSA-7rx3-28cr-v5wh in handlebars - Patched by Root","description":"Root has patched GHSA-7rx3-28cr-v5wh in the handlebars package for Root:npm. Multiple fixed versions available.","status":"published","published":"2026-10-15T16:35:43Z","modified":"2026-10-15T17:15:03.265448164Z","cvss":[{"version":"3.1","score":4.8,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:53:27.940619Z"},{"id":"UBUNTU-CVE-2026-33916","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, `resolvePartial()` in the Handlebars runtime resolves partial names via a plain property lookup on `options.partials` without guarding against prototype-chain traversal. When `Object.prototype` has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS. Version 4.7.9 fixes the issue. Some workarounds are available. Apply `Object.freeze(Object.prototype)` early in application startup to prevent prototype  pollution. Note: this may break other libraries, and/or use the Handlebars runtime-only build (`handlebars/runtime`), which does not compile templates  and reduces the attack surface.","status":"published","published":"2026-03-27T21:17:00Z","modified":"2026-10-17T12:58:11.075887493Z","cvss":[{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"},{"version":"3.1","score":4.7,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T16:56:16.036833Z"},{"id":"CLSA-2026-1782349135","title":"TuxCare security update for handlebars (8 CVEs)","description":"TuxCare rebuilt handlebars to address 8 CVEs. Fixed in 4.7.7-tuxcare.1.","status":"published","published":"2026-08-04T14:52:39Z","modified":"2026-10-15T09:47:10.129115848Z","updated_at":"2026-11-02T15:43:45.794021Z"},{"id":"CLSA-2026-1782435546","title":"TuxCare security update for handlebars (8 CVEs)","description":"TuxCare rebuilt handlebars to address 8 CVEs. Fixed in 4.7.8-tuxcare.1.","status":"published","published":"2026-08-04T14:52:39Z","modified":"2026-10-15T09:47:10.153121357Z","updated_at":"2026-11-02T15:43:45.794021Z"},{"id":"CLSA-2026-1785117463","title":"TuxCare security update for handlebars (5 CVEs)","description":"TuxCare rebuilt handlebars to address 5 CVEs. Fixed in 3.0.8-tuxcare.3.","status":"published","published":"2026-08-04T14:52:49Z","modified":"2026-10-15T09:47:36.940994671Z","updated_at":"2026-11-02T15:43:46.482572Z"},{"id":"CLSA-2026-1792139237","title":"TuxCare security update for handlebars (7 CVEs)","description":"TuxCare rebuilt handlebars to address 7 CVEs. Fixed in 4.5.3-tuxcare.3.","status":"published","published":"2026-10-16T08:27:17Z","modified":"2026-10-16T12:15:03.193874365Z","updated_at":"2026-11-02T15:43:50.949147Z"}]}}
