diff --git a/plugin-designs/pplr-data-maps/src/components/MapboxGeocoder.vue b/plugin-designs/pplr-data-maps/src/components/MapboxGeocoder.vue
index 96d3148b2..48bc15fd2 100644
--- a/plugin-designs/pplr-data-maps/src/components/MapboxGeocoder.vue
+++ b/plugin-designs/pplr-data-maps/src/components/MapboxGeocoder.vue
@@ -20,6 +20,7 @@
*/
import { ref, watch, onMounted, onBeforeUnmount, computed, inject } from 'vue'
import { ensureGeocoder, GEOCODER } from '../utils/pplrData.js'
+import { escHtml } from '../utils/pplrMap.js'
import { resolveMapProvider } from '@map/composables/useMapProvider.js'
import { ensureEngineScript, engineGlobal } from '@map/composables/useMapInstance.js'
import { useInstance } from '../composables/usePplrInstance.js'
@@ -108,13 +109,13 @@ function renderSuggestion(item) {
const local = item?.properties?.__pplr_local
const dotColor = local?.color || ''
const dot = dotColor
- ? ``
+ ? ``
: ''
const tag = kind
- ? `${kind}`
+ ? `${escHtml(kind)}`
: ''
return `
- ${dot}${item.place_name}${tag}
+ ${dot}${escHtml(item.place_name)}${tag}
`
}
diff --git a/plugin-designs/pplr-data-maps/src/utils/pplrMap.js b/plugin-designs/pplr-data-maps/src/utils/pplrMap.js
index 31d13e164..fa1d63aba 100644
--- a/plugin-designs/pplr-data-maps/src/utils/pplrMap.js
+++ b/plugin-designs/pplr-data-maps/src/utils/pplrMap.js
@@ -325,10 +325,15 @@ export async function addPGPoints(map, {
}
/** Standard popup HTML for a PG feature (PG-in-country grain). */
+// Every value interpolated into popup / suggestion HTML goes through this (CRITIC-3, the critic
+// fact-check). The data is PPLR's baked JSON and, for place search, the Mapbox geocoding API, and
+// setHTML() parses whatever it is handed; escaping costs nothing and closes the door.
+export const escHtml = (v) => String(v ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]))
+
export function popupForPG(p, extraRows = '') {
- const dash = v => (v == null || v === 'None' || v === '' ? '—' : v)
+ const dash = v => (v == null || v === 'None' || v === '' ? '—' : escHtml(v))
return `