{"id":"CVE-2026-33940","aliases":["CLSA-2026-1777163884","CLSA-2026-1782349135","CLSA-2026-1782435546","CLSA-2026-1785117463","CLSA-2026-1787070403","CLSA-2026-1792139237","DEBIAN-CVE-2026-33940","EUVD-2026-16860","GHSA-442j-39wm-28r2","GHSA-7rx3-28cr-v5wh","GHSA-xhpv-hc6g-r9c6","ROOT-APP-NPM-CVE-2026-33940","ROOT-APP-NPM-GHSA-7rx3-28cr-v5wh","UBUNTU-CVE-2026-33940"],"status":"published","title":"Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, 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. Version 4.7.9 fixes the issue. Some workarounds are available. First, use the runtime-only build (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable. Second, sanitize context data before rendering: Ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial. Third, avoid dynamic partial lookups (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","severity":{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00715,"percentile":0.54026,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-1321","CWE-367","CWE-843","CWE-94"],"published":"2026-03-27T18:21:44Z","modified":"2026-10-28T02:30:39Z","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.7-aikido.2","4.7.8-aikido.6","4.7.7-aikido.3","4.7.7-aikido.4","4.7.7-aikido.5"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"@rootio/handlebars","fixed_in":["4.7.8-root.io.2","4.7.8-root.io.3","4.7.8-root.io.4","4.7.8-root.io.5","4.7.7-root.io.1","4.7.7-root.io.2","4.7.8-root.io.6","4.7.7-root.io.3","4.7.7-root.io.4","4.7.7-root.io.5"],"sources":["osv"]}],"affected":[{"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":"ghsa","ecosystem":"npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"4.0.0"},{"last_affected":"4.7.8"},{"fixed":"4.7.9"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.0.3-tuxcare.0"},{"fixed":"3.0.3-tuxcare.2"}]}],"versions":["3.0.3","3.0.3-tuxcare.1"],"bundled":2},{"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":"2.0.0-tuxcare.0"},{"fixed":"2.0.0-tuxcare.5"}]}],"versions":["2.0.0","2.0.0-tuxcare.1","2.0.0-tuxcare.2","2.0.0-tuxcare.3","2.0.0-tuxcare.4"],"bundled":2},{"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.6.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":"Root:npm","package":"handlebars","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.7.7-aikido.2"},{"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.2"},{"fixed":"4.7.8-root.io.3"},{"fixed":"4.7.8-root.io.4"},{"fixed":"4.7.8-root.io.5"},{"fixed":"4.7.7-root.io.1"},{"fixed":"4.7.7-root.io.2"},{"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":"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"},{"vendor":"red hat","product":"cluster observability operator 1.5.0"},{"vendor":"red hat","product":"cryostat 4"},{"vendor":"red hat","product":"logging subsystem for red hat openshift"},{"vendor":"red hat","product":"red hat data grid 8"},{"vendor":"red hat","product":"red hat enterprise linux 10"},{"vendor":"red hat","product":"red hat enterprise linux 7"},{"vendor":"red hat","product":"red hat enterprise linux 8"},{"vendor":"red hat","product":"red hat enterprise linux 9"},{"vendor":"red hat","product":"red hat openshift ai (rhoai)"},{"vendor":"red hat","product":"red hat openshift dev spaces 3.27"},{"vendor":"red hat","product":"red hat process automation 7"}],"references":[{"url":"https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-xhpv-hc6g-r9c6","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://access.redhat.com/security/cve/CVE-2026-33940","tags":["vdb-entry","x_refsource_REDHAT","advisory"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2452521","tags":["issue-tracking","x_refsource_REDHAT","report"]},{"url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-33940.json","tags":["x_sadp-csaf-vex","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:34342","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:10175","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"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://nvd.nist.gov/vuln/detail/CVE-2026-33940","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-xhpv-hc6g-r9c6"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/33xxx/CVE-2026-33940.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-33940","tags":["advisory"]},{"url":"https://github.com/handlebars-lang/handlebars.js","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-33940","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-33940","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-33940","title":"Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, 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. Version 4.7.9 fixes the issue. Some workarounds are available. First, use the runtime-only build (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable. Second, sanitize context data before rendering: Ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial. Third, avoid dynamic partial lookups (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","status":"published","published":"2026-03-27T21:11:10.719Z","modified":"2026-09-04T12:04:53.335Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna"},{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"adp"}],"cwes":["CWE-94","CWE-843","CWE-94"],"updated_at":"2026-11-02T14:20:07.609948Z"}],"euvd":[{"id":"EUVD-2026-16860","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, 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. Version 4.7.9 fixes the issue. Some workarounds are available. First, use the runtime-only build (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable. Second, sanitize context data before rendering: Ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial. Third, avoid dynamic partial lookups (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","status":"published","published":"2026-03-27T21:11:10Z","modified":"2026-09-04T12:04:53Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T18:01:12.73639Z"}],"ghsa":[{"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"},{"id":"GHSA-xhpv-hc6g-r9c6","title":"Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial","description":"## Summary\n\nA 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.\n\n## Description\n\nThe vulnerable code path spans two functions in `lib/handlebars/runtime.js`:\n\n**`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.\n\n**`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.\n\n**Minimum prerequisites:**\n1. The template uses a dynamic partial lookup: `{{\u003e (lookup . \"key\")}}` or equivalent.\n2. The adversary can set the value of the looked-up context property to a crafted object.\n\nIn server-side rendering scenarios where templates process user-supplied context data, this enables full Remote Code Execution.\n\n## Proof of Concept\n\n```javascript\nconst Handlebars = require('handlebars');\n\nconst vulnerableTemplate = `{{\u003e (lookup . \"payload\")}}`;\n\nconst maliciousContext = {\n  payload: {\n    call: true, // bypasses the primary resolvePartial branch\n    type: \"Program\",\n    body: [\n      {\n        type: \"MustacheStatement\",\n        depth: 0,\n        path: {\n          type: \"PathExpression\",\n          parts: [\"pop\"],\n          original: \"this.pop\",\n          // Injected code breaks out of the generated function's argument list\n          depth: \"0])),function () {console.error('VULNERABLE: object -\u003e dynamic partial -\u003e RCE');}()));//\",\n        },\n      },\n    ],\n  },\n};\n\nHandlebars.compile(vulnerableTemplate)(maliciousContext);\n// Prints: VULNERABLE: object -\u003e dynamic partial -\u003e RCE\n```\n\n## Workarounds\n\n- **Use the runtime-only build** (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable.\n- **Sanitize context data** before rendering: ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial.\n- **Avoid dynamic partial lookups** (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","status":"published","published":"2026-03-27T18:21:44Z","modified":"2026-03-30T20:08:34Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-94","CWE-843"],"updated_at":"2026-11-02T14:39:46.119458Z"}],"nvd":[{"id":"CVE-2026-33940","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, 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. Version 4.7.9 fixes the issue. Some workarounds are available. First, use the runtime-only build (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable. Second, sanitize context data before rendering: Ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial. Third, avoid dynamic partial lookups (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","status":"published","published":"2026-03-27T22:16:21.03Z","modified":"2026-10-26T22:10:00.163Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"secondary"},{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-94","CWE-843"],"updated_at":"2026-11-02T14:12:11.678711Z"}],"osv":[{"id":"CVE-2026-33940","title":"Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, 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. Version 4.7.9 fixes the issue. Some workarounds are available. First, use the runtime-only build (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable. Second, sanitize context data before rendering: Ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial. Third, avoid dynamic partial lookups (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","status":"published","published":"2026-03-27T21:11:10.719Z","modified":"2026-10-28T02:30:39.341318356Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-843","CWE-94"],"updated_at":"2026-11-02T15:45:01.896044Z"},{"id":"DEBIAN-CVE-2026-33940","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, 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. Version 4.7.9 fixes the issue. Some workarounds are available. First, use the runtime-only build (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable. Second, sanitize context data before rendering: Ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial. Third, avoid dynamic partial lookups (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","status":"published","published":"2026-03-27T22:16:21.03Z","modified":"2026-10-27T10:00:52.638229248Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T15:45:54.587575Z"},{"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":"GHSA-xhpv-hc6g-r9c6","title":"Handlebars.js has JavaScript Injection via AST Type Confusion when passing an object as dynamic partial","description":"## Summary\n\nA 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.\n\n## Description\n\nThe vulnerable code path spans two functions in `lib/handlebars/runtime.js`:\n\n**`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.\n\n**`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.\n\n**Minimum prerequisites:**\n1. The template uses a dynamic partial lookup: `{{\u003e (lookup . \"key\")}}` or equivalent.\n2. The adversary can set the value of the looked-up context property to a crafted object.\n\nIn server-side rendering scenarios where templates process user-supplied context data, this enables full Remote Code Execution.\n\n## Proof of Concept\n\n```javascript\nconst Handlebars = require('handlebars');\n\nconst vulnerableTemplate = `{{\u003e (lookup . \"payload\")}}`;\n\nconst maliciousContext = {\n  payload: {\n    call: true, // bypasses the primary resolvePartial branch\n    type: \"Program\",\n    body: [\n      {\n        type: \"MustacheStatement\",\n        depth: 0,\n        path: {\n          type: \"PathExpression\",\n          parts: [\"pop\"],\n          original: \"this.pop\",\n          // Injected code breaks out of the generated function's argument list\n          depth: \"0])),function () {console.error('VULNERABLE: object -\u003e dynamic partial -\u003e RCE');}()));//\",\n        },\n      },\n    ],\n  },\n};\n\nHandlebars.compile(vulnerableTemplate)(maliciousContext);\n// Prints: VULNERABLE: object -\u003e dynamic partial -\u003e RCE\n```\n\n## Workarounds\n\n- **Use the runtime-only build** (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable.\n- **Sanitize context data** before rendering: ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial.\n- **Avoid dynamic partial lookups** (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","status":"published","published":"2026-03-27T18:21:44Z","modified":"2026-09-10T03:50:41.814438241Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-843","CWE-94"],"updated_at":"2026-11-02T15:47:00.198893Z"},{"id":"ROOT-APP-NPM-CVE-2026-33940","title":"CVE-2026-33940 in handlebars - Patched by Root","description":"Root has patched CVE-2026-33940 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.25046568Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"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-33940","description":"Handlebars provides the power necessary to let users build semantic templates. In versions 4.0.0 through 4.7.8, 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. Version 4.7.9 fixes the issue. Some workarounds are available. First, use the runtime-only build (`require('handlebars/runtime')`). Without `compile()`,  the fallback compilation path in `invokePartial` is unreachable. Second, sanitize context data before rendering: Ensure no value in the context is a non-primitive  object that could be passed to a dynamic partial. Third, avoid dynamic partial lookups (`{{\u003e (lookup ...)}}`) when context data is user-controlled.","status":"published","published":"2026-03-27T22:16:00Z","modified":"2026-10-17T12:58:11.070468831Z","cvss":[{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"},{"version":"3.1","score":8.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T16:56:16.036833Z"},{"id":"CLSA-2026-1777163884","title":"TuxCare security update for handlebars (2 CVEs)","description":"TuxCare rebuilt handlebars to address 2 CVEs. Fixed in 3.0.3-tuxcare.2.","status":"published","published":"2026-08-04T14:52:28Z","modified":"2026-10-15T09:45:48.681742832Z","updated_at":"2026-11-02T15:43:44.384419Z"},{"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-1787070403","title":"TuxCare security update for handlebars (2 CVEs)","description":"TuxCare rebuilt handlebars to address 2 CVEs. Fixed in 2.0.0-tuxcare.5.","status":"published","published":"2026-08-18T16:26:43Z","modified":"2026-10-15T09:47:11.811218942Z","updated_at":"2026-11-02T15:43:47.20725Z"},{"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"}]}}
