---
status: pending
priority: p1
issue_id: "001"
tags: [code-review, security, xss, frontend]
dependencies: []
---
# XSS via Node Names in Google Maps InfoWindow
## Problem Statement
`showInfoWindow` in `NetworkMap.tsx` builds an HTML string using `nodeName` (sourced from `node.longName || node.shortName`, which comes from MQTT packets) and passes it to `infoWindowRef.current.setContent(infoContent)`. Google Maps `InfoWindow.setContent` renders its argument as live HTML. A Meshtastic device with a `longName` of `
` will execute arbitrary JavaScript in every viewer's browser.
Attack path: malicious device broadcasts NodeInfo on public MQTT → Go decoder stores it faithfully → SSE streams to browser → Redux stores verbatim → map renders with no sanitization.
## Findings
- **File:** `web/src/components/dashboard/NetworkMap.tsx`, lines 431-451
- `nodeName` is interpolated raw into an HTML template literal
- `infoWindowRef.current.setContent(infoContent)` renders the HTML directly
- All SVG marker templates on lines 344 and 390 use the same unsafe pattern (lower risk currently since values are internally computed, but should be normalized)
- No sanitization at any layer (decoder, SSE, Redux, component)
## Proposed Solutions
### Option A: DOM construction with .textContent (Recommended)
Replace the HTML template literal with `document.createElement` tree. Use `.textContent` for all data-derived values.
- **Pros:** Completely eliminates XSS, no extra dependency, idiomatic
- **Cons:** More verbose than template literal
- **Effort:** Small
- **Risk:** Low
### Option B: Pass Element directly to setContent
Build a `div` element using DOM APIs and pass the element object to `setContent` (which accepts `Element | string`).
- **Pros:** Clean, no string HTML at all
- **Cons:** Slightly more DOM manipulation code
- **Effort:** Small
- **Risk:** Low
### Option C: Add DOMPurify sanitization
Run `nodeName` through `DOMPurify.sanitize()` before interpolation.
- **Pros:** Minimal code change
- **Cons:** Adds a dependency; still uses HTML string pattern; sanitizers can be bypassed
- **Effort:** Small
- **Risk:** Medium (sanitizer bypass potential)
## Recommended Action
_Use Option A — DOM construction. It eliminates the vulnerability class entirely rather than mitigating it._
## Technical Details
- **Affected files:** `web/src/components/dashboard/NetworkMap.tsx`
- **Function:** `showInfoWindow` (line ~412)
- **Data origin:** `data.nodeInfo.longName` / `data.nodeInfo.shortName` from MQTT protobuf
## Acceptance Criteria
- [ ] `showInfoWindow` constructs InfoWindow content using DOM APIs, not HTML strings
- [ ] No user-supplied string data is interpolated into HTML template literals in NetworkMap.tsx
- [ ] A node with `longName = ""` renders safely as literal text in the info window
## Work Log
- 2026-03-15: Identified by security-sentinel and architecture-strategist review agents