MeshCore's problem with security

10 min read Original article ↗

At the beginning of this year, I reported a security vulnerability in MeshCore. The process of discovering this vulnerability as well as the handling of security reports made me want to write a post on security in MeshCore. Enjoy!

MeshCore is a mesh communication project, allowing the construction of ad-hoc networks between LoRa radio devices. It's mostly used by tech enthusiasts who want to explore a non-internet-based communication method. Think WhatsApp but through an antenna. You may have also heard of it recently due to a split of the project away from Andy Kirby, who has been accused of producing too much AI slop code and is trying to trademark the MeshCore name in the UK. This may have also gotten me to finally write this post.

Screenshot of MeshCore's website. It says 'Off-Grid, Open-Source Encrypted Messaging' in a big title, with the encryption part highlighted.
MeshCore's website. I sure hope we can trust them with our private chats...

MeshCore was created by Scott Powell in 2024, as an alternative to existing mesh networking projects like Meshtastic and Reticulum. Scott lists the main reasons as being the desire for an open ecosystem (somewhat ironic, as we'll see later), an evolving and extensible protocol and being privacy-focused, among an extensive list of other reasons.

MeshCore has greatly improved the mesh networking ecosystem by making path-based routing mainstream. This allows meshes to scale to many more nodes than the more classic flood-fill approach. Having only dedicated devices repeat/route messages instead of every client/chat device doing it also reduces cross-talk as well as the amount of badly-positioned repeater nodes1, which Meshtastic struggles with. In contrast to Meshtastic, it also minimizes the number of messages sent by default, such as the infamous telemetry messages that block people from talking and instead sends (mostly) useless info.

By contrast, MeshCore's approach to security remains lacking, both in secure coding practices and in communicating problems to users, as the vulnerability I reported illustrates.

The Vulnerability🔗

In late 2025, I started looking at MeshCore's code, as I heard it was the cool new kid on the block. Almost immediately, I noticed the hallmarks of vulnerable embedded code: manually implemented parsing and serialization of data, raw pointers everywhere, manual bounds checks, implicit integer conversions, raw memcpy and disabled compiler warnings.

After searching through the firmware code a bit and understanding how data is received and parsed, I found a piece of code handling incoming packets that seemed suspicious. It simply read a length value from the packet, performed no checks on it and passed it to another function.

if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
  int k = 0;
  uint8_t path_len = data[k++]; // <----------------
  uint8_t* path = &data[k]; k += path_len;
  uint8_t extra_type = data[k++] & 0x0F;
  uint8_t* extra = &data[k];
  uint8_t extra_len = len - k;
  if (onPeerPathRecv(pkt, j, secret, path, path_len, extra_type, extra, extra_len)) {
  // ...

That function eventually uses that unchecked length to copy into a fixed size buffer, leading to a classic heap-based buffer overflow vulnerability.

bool BaseChatMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
  // ...
  return onContactPathRecv(from, packet->path, packet->path_len, path, path_len, extra_type, extra, extra_len);
}

bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
  // from.out_path has a fixed size of 64 bytes
  memcpy(from.out_path, out_path, from.out_path_len = out_path_len);
  // ...
}

Funnily enough, the amount of data we can overwrite is limited by an integer coercion bug when assigning to from.out_path_len, where the length is cast from an unsigned uint8_t to int8_t, then to a size_t for the memcpy, which causes all values >= 128 to be converted to a massive value, making the memcpy crash the device and enabling a denial-of-service.

This second mistake might have been a blessing in disguise, though, as the limit of 127 overwritable bytes prevents a more dangerous attack: the destination buffer is stored in the list of contacts (inside ContactInfo::out_path), which contains the public key of known nodes. This would have allowed an attacker to overwrite their own public key onto a contact, impersonating that user's name and allowing them to send and receive messages to the affected node on their behalf.

struct ContactInfo {
  mesh::Identity id;                            // can be partially overwritten on the next entry
  char name[32];
  uint8_t type;
  uint8_t flags;
  int8_t out_path_len;
  mutable bool shared_secret_valid;
  uint8_t out_path[MAX_PATH_SIZE];              // we overflow this buffer
  uint32_t last_advert_timestamp;               // overwritten
  uint32_t lastmod;                             // overwritten
  int32_t gps_lat, gps_lon;                     // overwritten
  uint32_t sync_since;                          // overwritten
  mutable uint8_t shared_secret[PUB_KEY_SIZE];  // overwritten
};

Even with a limited amount of overwritable bytes, another attack can be performed. If the attacker places a contact at the end of the list (trivial for nodes that have automatic contacts enabled), they can overwrite the field that holds the number of stored contacts, for example with 0. This caused the device to delete all contacts, as well as the app to lose all of them, essentially resetting the node.

This vulnerability is fixed in version v1.14.0, so make sure your nodes are updated to at least this version.

Disclosure Process🔗

Because there is no security contact information listed anywhere on the MeshCore project, I did what you do with modern software projects and joined the Discord. There I got in contact with a developer. I sent them a report and informed them about the 90 day disclosure timeline. The report was acknowledged.

After that, I was not contacted again. Two weeks before the disclosure deadline, I checked the latest releases, which showed no indication that the problem was fixed. Only by reviewing the code manually did I discover that a length check had been added, fixing the vulnerability.

I contacted the developer and told them to set up GitHub security advisories, so users could be informed about vulnerabilities and what versions have fixed them. I was told "that's not a bad idea", and never heard back after that. Advisories were never configured, and the vulnerability was never communicated to the MeshCore community, leaving them in the dark.

This seems to be a pattern. There are various commits that add length checks to the codebase, which I suspect are security-relevant, but no vulnerabilities are disclosed anywhere2. The process seems to be silently fixing vulnerabilities without letting anyone know they should update.

Comparison to Meshtastic🔗

I also reported a security vulnerability to Meshtastic last year, CVE-2025-24797. As you may notice from the fact that this vulnerability actually has a CVE ID, Meshtastic's disclosure process worked a little better.

I was able to use the security tab on the repository to make a report. I was then able to see that a developer was assigned to the problem and could communicate with them and others through comments and a private fork, giving advice on the proposed fix. Other maintainers could also check the progress and comment on the report.

After the fix was released and there was some time to test it, the vulnerability advisory was published, showing the possible impact and fixed version. Users picked up on this and updated their devices, ensuring this vulnerability could not be exploited.

This was how I would hope a disclosure is handled.

Code quality🔗

In addition to not disclosing vulnerabilities, the MeshCore firmware source code is also questionable from a security standpoint.

The most obvious problem is the use of raw C arrays, combined with brittle bounds checking and manual byte-level manipulation. Any time a developer forgets to implement the correct bounds check or changes an existing calculation, there is a large risk of an out-of-bounds access. Arrays are also passed using a pointer + length, allowing confusion between lengths of different arrays, instead of binding one to the other.

Another issue is the frequent occurrence of implicit integer casts, causing absurd values that can lead to bugs or crashes.

The codebase also lacks automated tests (thanks to mtlynch for trying to change that), which means there's no verification of functionality, be it security-relevant or not.

All of these issues combined stopped me from searching for more security problems to report, as I expect a large amount of (at least denial-of-service) vulnerabilities to exist in MeshCore.

Cryptography🔗

Payload encryption currently uses plain AES-128 block encryption plus a two-byte message authentication code (MAC). These are both questionable choices, as plain AES-128 without padding, chaining, or an IV/nonce allows an attacker to tell if you've sent the same message twice, reorder blocks (if the MAC is also defeated), and analyze the length of your messages. The MAC uses HMAC-SHA256, but is truncated from 32 bytes to 2 bytes, making brute-forcing feasible, with a 100% success rate after 65'536 attempts. Assuming one message every 5 seconds, this can be achieved within 4 days, breaking any authenticity guarantees.

There is an issue on the MeshCore GitHub project that has been open since last year, but no significant progress has been made in switching to more secure cryptographic primitives.

Open ecosystem?🔗

As mentioned in the introduction, one of the goals of MeshCore was to have an open ecosystem. This is a noble goal, however, one notable difference from other mesh systems is the fact that the official MeshCore app is completely closed-source. Due to this, I was not able to check for any interesting interactions between the companion firmware and the app itself (reverse-engineering sounds like effort, security-through-obscurity wins again). It would be nice if the community could also contribute here, which could still be done if the app had some kind of open-core approach, where most code was accessible but some paid features were not open source.

Luckily, there's MeshCore Open, an actually open-source client app. So I guess you could say the ecosystem is indeed open, just perhaps not in the way intended by the maintainers.

What can be done🔗

The most glaring problem with MeshCore is that the maintainers do not openly communicate vulnerabilities. Users are left without knowledge of any problems, unable to judge whether to trust MeshCore with their private communication.

To remedy this, I recommend keeping a list of security vulnerabilities, including vulnerable and fixed versions and CVE IDs. The easiest way to achieve this would be to use the GitHub security policy feature, which allows secure reporting as well as CVE assignments and disclosure. Maintainers should also proactively create advisories for issues that are fixed by the developers before anyone reports them, if they are security-relevant.

On the code quality side, I've got a few different recommendations:

  • Use some kind of serialization / deserialization library to eliminate manual byte array processing
  • Use more targeted data types to prevent unclear/buggy code
    • consistent usage of signed and unsigned
    • std::array and std::span make it easier to avoid out-of-bounds access
  • Enable compiler warnings to find various issues and elevate them to errors to keep quality high
  • Set up a fuzzing toolchain

Implementing at least some of these technical measures is the minimum to place any trust in MeshCore's security.

Conclusion🔗

I cannot recommend using MeshCore for private or sensitive communication, due to both the handling of security problems and the general state of the codebase. I hope the project can improve its current state and stay true to its promise of offering a secure mesh networking solution.

  1. Repeater / router nodes should be in high locations so they have a large range. There should also be as few as necessary, as too many can interfere with each other while sending.

  2. Commits such as b67decfb, 45564bad, b6110eee