Fix XSS in map popups by replacing setHTML with setDOMContent

- POI popup: use DOM API with textContent (auto-escapes) instead of template literal in setHTML
- Listing popup: replace renderToString + setHTML with createRoot + setDOMContent for proper React lifecycle
This commit is contained in:
Viktor Barzin 2026-02-08 19:42:44 +00:00
parent 0a9a83507e
commit 727dd537ef
No known key found for this signature in database
GPG key ID: 0EB088298288D958

View file

@ -3,7 +3,7 @@ import mapboxgl from "mapbox-gl";
import 'mapbox-gl/dist/mapbox-gl.css';
import { useEffect, useRef, useMemo, useCallback } from "react";
import { Crosshair } from "lucide-react";
import { renderToString } from 'react-dom/server';
import { createRoot } from 'react-dom/client';
import "../assets/Map.css";
import { Metric, type ParameterValues } from "./FilterPanel";
import { PropertyCard } from "./PropertyCard";
@ -253,11 +253,18 @@ export function Map(props: MapProps) {
el.style.cssText = 'width:24px;height:24px;background:#ef4444;border:2px solid white;border-radius:50%;box-shadow:0 2px 4px rgba(0,0,0,0.3);cursor:pointer;';
el.title = poi.name;
const popupContent = document.createElement('div');
popupContent.style.cssText = 'padding:4px 8px';
const nameEl = document.createElement('strong');
nameEl.textContent = poi.name;
const addressEl = document.createElement('span');
addressEl.style.cssText = 'color:#666;font-size:12px';
addressEl.textContent = poi.address;
popupContent.append(nameEl, document.createElement('br'), addressEl);
const marker = new mapboxgl.Marker({ element: el })
.setLngLat([poi.longitude, poi.latitude])
.setPopup(new mapboxgl.Popup({ offset: 12 }).setHTML(
`<div style="padding:4px 8px"><strong>${poi.name}</strong><br/><span style="color:#666;font-size:12px">${poi.address}</span></div>`
))
.setPopup(new mapboxgl.Popup({ offset: 12 }).setDOMContent(popupContent))
.addTo(mapRef.current);
poiMarkersRef.current.push(marker);
@ -352,12 +359,15 @@ export function Map(props: MapProps) {
const properties = heatmapRef.current._tree.search(searchBounds);
if (properties.length > 0) {
const listingDialogPopup = getListingDialog(properties);
new mapboxgl.Popup()
const container = document.createElement('div');
const root = createRoot(container);
root.render(getListingDialog(properties));
const popup = new mapboxgl.Popup()
.setLngLat([longitude, latitude])
.setHTML(renderToString(listingDialogPopup))
.setDOMContent(container)
.setMaxWidth("450px")
.addTo(mapRef.current);
popup.on('close', () => root.unmount());
}
}