// Shared HTML-escaping helpers. All Sentry / MCP / agent-derived values are // interpolated into HTML string templates, so every dynamic text or attribute // value MUST pass through here before it reaches the webview. const HTML_ENTITIES = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', } /** Escape a value for use as HTML text or a double/single-quoted attribute. */ export function escapeHtml(value) { if (value == null) return '' return String(value).replace(/[&<>"']/g, (ch) => HTML_ENTITIES[ch]) } /** * Return a safe href: only http(s) URLs are allowed through, everything else * (javascript:, data:, malformed, etc.) collapses to '#'. The result is still * attribute-escaped for embedding. */ export function safeHref(value) { if (value == null) return '#' const raw = String(value).trim() try { const url = new URL(raw) if (url.protocol === 'http:' || url.protocol === 'https:') { return escapeHtml(url.href) } } catch { // not an absolute URL — fall through } return '#' } /** * Serialize a value as JSON safe to embed inside an inline