Rooting Home Assistant through MeshCore: XSS attacks with a LoRa node name

WhyNotHugo1 pts0 comments

Rooting Home Assistant through MeshCore: XSS attacks with a LoRa node nameRooting Home Assistant through MeshCore: XSS attacks with a LoRa node name<br>June 3, 2026

` as the node name." title="LILYGO T3-S3 LoRa node with antenna attached, OLED screen showing `` as the node name." loading=lazy width=800 height=1067>A crafted MeshCore node name could compromise any Home Assistant<br>instance running meshcore-card<br>as soon as someone viewed a dashboard with that card. MeshCore relays<br>through repeaters, so the attacker did not need to be in radio range<br>of the target itself, only of any node that could forward to it.<br>That was enough to compromise a Home Assistant OS instance, up<br>to remote root on the host through add-ons (with typical<br>settings). That includes a full takeover of anything Home Assistant can reach: lights,<br>locks, cameras, scripts.<br>The same XSS (cross-site scripting) pattern appears to be present in<br>MeshCore-Home-Assistant-Panel-v2<br>and its<br>HACS variant,<br>based on source review. I did not get the panels running myself,<br>so I have not verified this with running code.<br>This also affects at least 20 public MeshCore analyzer websites,<br>including instances of CoreScope.<br>These websites typically have little sensitive data, so the impact is limited.<br>The bug is live: I confirmed HTML injection on at<br>least five Home Assistant instances belonging to others1,<br>by broadcasting a node name containing<br>a benign tag on a public mesh and watching my logs.<br>I stopped there rather than push a payload that runs javascript.<br>The bug was fixed in meshcore-card v0.3.3, assigned CVE-2026-45323. The panel-v2 variants remain unpatched; their maintainer has not responded to disclosure attempts since March 2026.<br>I have not made separate reports to MeshCore analyzer websites.<br>What is MeshCore?<br>MeshCore is an open-source mesh networking<br>protocol that runs over LoRa radio. LoRa is a long-range,<br>low-bandwidth radio technology in license-free sub-GHz bands<br>(typically 868 MHz in Europe). MeshCore is one of<br>several mesh stacks built on LoRa.<br>Nodes in a MeshCore mesh periodically broadcast an advertisement<br>packet announcing themselves. Each advertisement contains an adv_name field:<br>a 32-byte string, null-terminated,<br>with no character validation at the protocol level2.<br>Any node can pick<br>any name. To be clear, the vulnerability is in software that renders those<br>names in HTML, not in MeshCore itself.<br>LoRa typically reaches a few kilometres, sometimes longer. The attacker does not need to<br>share a network or a building with the target.<br>Being able to reach any node in the same mesh is enough, as MeshCore has<br>repeater nodes that can relay advertisements.<br>The Netherlands has a dense repeater<br>network, with almost 1800 repeaters, stretching even beyond the borders.<br>meshcore-card XSS<br>meshcore-card is a<br>component for HACS, the Home Assistant Community Store, that renders<br>MeshCore state in Home Assistant dashboards.<br>It provides three card types.<br>The contact card is the most exposed: it renders every<br>entry in the contact list, including contacts added automatically<br>from heard advertisements. A malicious adv_name lands on the<br>dashboard as soon as someone views it.<br>Nothing between the radio and the DOM restricts the value3.<br>meshcore-card renders heard contacts for up to max_contact_age_days<br>(default 7) after the last advertisement, so a single heard broadcast<br>impacts every dashboard render for a week, not only while the attacker<br>is transmitting.<br>CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H, score 9.6 (critical).<br>Tracked as CVE-2026-45323<br>and<br>GHSA-5vrg-xpcj-xppc.<br>Size-constrained payloads<br>The payload, which I am still broadcasting today from one of my own nodes,<br>is a benign tracking pixel:

That is 23 bytes. A real XSS would need to load executable code<br>from somewhere.<br>In OpenWrt<br>I solved this with two broadcasts: one carrying<br>and another carrying 4. This relies on<br>a DOM quirk:<br>elements with an id attribute are automatically exposed as named<br>properties on window, so makes the anchor available as<br>window.a. My OpenWrt payload rendered into the main document,<br>where that lookup works.<br>meshcore-card renders into a<br>shadow DOM<br>inside its custom element, and that named-element shortcut only<br>populates from the main document, not from shadow roots. The anchor<br>lands in the shadow tree but window.a stays undefined.<br>My solution uses three broadcasts of 16, 29, and 31 bytes,<br>including an iframe, to run code of any length in the HA session.

'><br>The first opens an iframe with<br>srcdoc=',<br>the third closes it with '.<br>The HTML parser absorbs every character in between as the srcdoc<br>value, including the other advertised node names along with the<br>card chrome between them.<br>The iframe parses that absorbed string as a fresh HTML<br>document. Inside that fresh document the named-element shortcut<br>works again, fires, and because<br>srcdoc iframes inherit the embedder&rsquo;s origin,<br>the imported module runs with full access to the Home Assistant<br>frontend, while each inserted fragment fits in 32...

meshcore node home assistant card lora

Related Articles