Security review: GHSA-xq3m-2v4x-88gg (protobuf.js RCE) — MeshMonitor is not exploitable #2727
Yeraze
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
TL;DR
MeshMonitor is NOT exploitable by GHSA-xq3m-2v4x-88gg ("Critical flaw in Protobuf library enables JavaScript code execution", reported by Endor Labs 2026-04-18). The vulnerability requires loading an attacker-controlled
.protoschema. MeshMonitor only loads static, bundled Meshtastic.protofiles shipped inside the image — there is no code path that accepts schemas from users, devices, MQTT, or any other external source.The currently-released main branch (
4.0.0-beta6) and the pinnedpackage-lock.jsonalready resolveprotobufjs@8.0.1(the patched version). No action required for deployed operators.The vulnerability (GHSA-xq3m-2v4x-88gg)
protobufjs(npm)<= 8.0.0on the 8.x line,<= 7.5.4on the 7.x line8.0.1(npm release 2026-04-04) and7.5.5(npm release 2026-04-15)new Function()string concatenation)Functionconstructor. Schema-side identifiers are not sanitized, so a malicious.protowhose type name contains)};<attacker-js>//escapes the synthetic function body and achieves RCE in the Node.js process that loads the schema..protodefinition.Functionat all.MeshMonitor's usage of protobufjs
All protobuf usage is server-side (
src/server/). The frontend never touches protobufjs directly.Where schemas are loaded
Two call sites, both load from hard-coded paths:
src/server/protobufLoader.ts— loadsmesh.proto,admin.proto,apponly.proto,paxcount.proto,mqtt.proto,storeforward.protosrc/server/protobufService.ts— loadsmeshtastic/mesh.proto,meshtastic/admin.protoEvery
root.load()call usespath.join(protoDir, 'meshtastic/<literal-filename>.proto'). There is zero user input in any schema path.Where the
.protofiles come fromprotobufs/git submodule, pinned to upstreammeshtastic/protobufs@v2.7.20(cdde2876befc50620307497e269f313c7944fc0b).Dockerfilecopiesprotobufs/into the image at build time, and the built server reads from that path inside the container..protofile. No multer/upload handler touches the.protodirectory. No API endpoint accepts schema content.Where types are looked up
Over 150 call sites in
src/server/invokeroot.lookupType('meshtastic.<TypeName>'). I audited the full list — every single type name is a hard-coded string literal. None are constructed fromreq.body,req.params,req.query, or any other user-influenced source. Examples:'meshtastic.FromRadio','meshtastic.MeshPacket','meshtastic.AdminMessage','meshtastic.Position','meshtastic.Telemetry'.Where decode is called
All
Type.decode(data)sites decode message bytes against the trusted schemas loaded at startup. The message-decode path is not the attack surface described in the advisory — only schema loading is.What we do NOT do
Root.fromJSON()/Root.addJSON()/protobuf.parse()of user input.protobuf.rootscache..protocompilation at runtime.Dependency state
package.jsondeclares"protobufjs": "^8.0.1"(patched range). The committedpackage-lock.jsononmainpins the exact resolved version:The
v4.0.0-beta6Docker image and anynpm ciinstall from the locked manifest get the patched build.Threat model summary
.proto.protoServiceEnvelope/MeshPacketbytes.protofiles are baked into the image.protothrough our rootRecommendation for operators
v4.0.0-beta6or later, or build frommain: no action required. The patchedprotobufjsis already installed.package-lock.json: verifynpm ls protobufjsreports>= 8.0.1or>= 7.5.5. If not, re-runnpm install --package-lock-only --legacy-peer-depsand rebuild.protobufs/submodule as trusted code and only update it by bumping to a tagged upstream release.Why this was low-risk for us by design
MeshMonitor's protobuf surface is intentionally narrow:
This aligns with Endor Labs' long-term guidance in the advisory: "treat schema-loading as untrusted input" — and we already do, by never exposing it as input at all.
References
protobufjs@8.0.1/protobufjs@7.5.5All reactions