TL;DR

Handlebars (41,542,245 npm downloads in the week to 2026-10-07) fixed two JavaScript-injection bugs in 4.7.10 on 2026-10-05. CVE-2026-106446 (CVSS 9.8): Handlebars.compile() and precompile() accept a pre-parsed AST object as well as a template string; version 4.7.9 (2026-03-26, for CVE-2026-33937) added validation for only three node types, and the compiler still writes other AST fields such as Program.blockParams.length straight into generated JavaScript. An application that passes an untrusted object, for example a parsed JSON body, to compile() runs attacker code when the template renders; with precompile() the code is embedded in the output and runs wherever that is loaded. CVE-2026-106445 (CVSS 4.0 9.2): lookupProperty returns Function.prototype.constructor before the prototype-access deny list runs, so a template rendered with allowProtoMethodsByDefault: true and a function in its context can reach the Function constructor. Applications that only ever pass template strings to compile() and keep the default proto settings are not affected by either. Upgrade to 4.7.10 anyway; the scanner noise alone will force it.

What happened

The maintainers published both advisories on the repository on 2026-10-05, GitHub as CNA issued the CVEs on 2026-10-06, and the reviewed-critical npm listing carried both on 2026-10-08.

CVE-2026-106446 (GHSA-8r5x-fm3f-whwj), 9.8, CWE-94/843, 4.0.0 to 4.7.9, fixed 4.7.10. This is the second incomplete-fix round for the same entry point. In March, CVE-2026-33937 (9.8) showed that a NumberLiteral.value in a crafted AST was emitted unquoted into generated code, and CVE-2026-33940 (8.1) showed a dynamic partial lookup could hand an object to env.compile(); 4.7.9 added a validator for PathExpression, NumberLiteral and BooleanLiteral nodes. The new advisory lists the fields the validator never looked at: Program.blockParams.length works with default options; depth, a non-string StringLiteral.value and a non-string PathExpression.original need stringParams: true. Sixteen reporters are credited across three variants, including Team Atlanta and Shopify's open-source security team, which is the pattern of a bug class being fuzzed in parallel once the first instance is public.

CVE-2026-106445 (GHSA-p8wg-vrv2-v86f), 9.2 (CVSS 4.0, AT:P), CWE-184/1289, 4.0.0 to 4.7.9, fixed 4.7.10. The runtime's prototype-access protection uses a deny list, but constructor is an own property of Function.prototype, so the lookup succeeds before the list is consulted. The precondition is a template the attacker controls, rendered with allowProtoMethodsByDefault: true, and a function reachable from the context. The vendor's workaround is simply not to set that option for untrusted templates. Reporter brandon-t-elliott.

Who is actually exposed. The 9.8 is an unauthenticated network score for the shape "server passes a request body to compile()". Most generated apps compile string templates from their own source tree, where neither bug applies. The exposed shapes are: a template editor or notification service that stores user-supplied templates and compiles them per request; an API that accepts JSON and forwards it to compile() without a type check; or a build pipeline that precompiles templates from untrusted input. Filed high rather than critical for that reason, with both registry scores shown.

Am I affected?

npm ls handlebars 2>/dev/null                       # anything below 4.7.10
grep -rn "Handlebars.compile\|Handlebars.precompile\|allowProtoMethodsByDefault\|stringParams" --include=*.js --include=*.ts . | grep -v node_modules | head

If every compile() call site receives a string literal or a string read from your own files, you are not exploitable on any version. If any call site can receive a parsed object, or you set allowProtoMethodsByDefault: true for user templates, you are.

If you are affected

  1. Upgrade handlebars to 4.7.10 or later; regenerate the lockfile.
  2. Add typeof template === 'string' before every compile() / precompile() call, and reject anything else. The vendor lists this as the workaround for both March and October bugs.
  3. Servers that only render precompiled templates should import handlebars/runtime, where compile() does not exist.
  4. Do not set allowProtoMethodsByDefault: true for templates you did not write.
  5. If an untrusted object could have reached compile() on an affected version, treat it as code execution and follow if-your-webapp-was-compromised.md.

Prevention

Template engines are compilers; give them strings, never structures from the network (auditing-a-vibe-coded-repo.md). Keep the runtime-only build in production, and let npm audit block the deploy when a 9.8 lands in a library this widely installed (npm-hardening.md).

Sources