From 727dd537ef370f50c5591e710a0ec4cd0fe71a39 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sun, 8 Feb 2026 19:42:44 +0000 Subject: [PATCH] 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 --- frontend/src/components/Map.tsx | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Map.tsx b/frontend/src/components/Map.tsx index 7c7858d..ff6b1bc 100644 --- a/frontend/src/components/Map.tsx +++ b/frontend/src/components/Map.tsx @@ -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( - `
${poi.name}
${poi.address}
` - )) + .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()); } }