diff --git a/internal/check-popup-escaping.mjs b/internal/check-popup-escaping.mjs
new file mode 100644
index 000000000..5593db580
--- /dev/null
+++ b/internal/check-popup-escaping.mjs
@@ -0,0 +1,46 @@
+#!/usr/bin/env node
+/**
+ * check-popup-escaping — the library's map popups cannot be made to run script by the DATA. Headless.
+ *
+ * CRITIC-3 (2026-09-26): generatePinPopupContent and generateRegionPopupContent (useMapEvents.js)
+ * interpolated people-group and country fields raw into HTML handed to setHTML(). A name or description
+ * like `
` from an API or a partner's list would run in the host page.
+ * Mounts the real composable in a tiny Vue app and renders both popups from hostile data.
+ * ASSERTS no raw tag, event-handler attribute or javascript: URL survives; ordinary text still shows.
+ * EXIT 0 pass · 1 fail
+ */
+import { createApp, h } from 'vue'
+import { createI18n } from 'vue-i18n'
+import { readFileSync } from 'node:fs'
+import { resolve, dirname } from 'node:path'
+import { fileURLToPath } from 'node:url'
+globalThis.__INTLIFY_JIT_COMPILATION__ = true
+const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
+const { useMapEvents } = await import('../library/composables/useMapEvents.js')
+const popup = JSON.parse(readFileSync(resolve(ROOT, 'library/i18n/locales/en/popup.json'), 'utf8'))
+const EVIL = '
'
+const pin = { name: 'Afar ' + EVIL, country: EVIL, status: EVIL, language: EVIL, languageFamily: EVIL, population: EVIL,
+ description: EVIL, imageUrl: 'javascript:alert(3)' }
+let out = {}
+const app = createApp({ setup() {
+ const ev = useMapEvents({ getNormalizedPeopleGroups: () => [{ countryIso: 'ETH', wagfRegionLabel: EVIL, wagfBlockLabel: EVIL }] })
+ out.pin = ev.generatePinPopupContent(pin)
+ out.region = ev.generateRegionPopupContent({ iso_3166_1_alpha_3: 'ETH', name_en: 'Ethiopia ' + EVIL })
+ return () => h('div')
+} })
+app.use(createI18n({ legacy: false, locale: 'en', messages: { en: { popup } } }))
+app.provide('uiStore', {})
+const { renderToString } = await import('vue/server-renderer')
+await renderToString(app)
+let bad = 0
+for (const [k, html] of Object.entries(out)) {
+ const probs = []
+ if (/
/i.test(html)) probs.push("a raw
/