This commit is contained in:
2026-02-26 10:53:33 +01:00
parent f09516e9dc
commit c577f56d96
2 changed files with 31 additions and 1 deletions

View File

@@ -207,13 +207,34 @@ function applyChatUI(cfg) {
// ─── Chat UI helpers ────────────────────────────────────────── // ─── Chat UI helpers ──────────────────────────────────────────
/** Sanitise bot HTML: allow only <a href="..." target="...">…</a>, escape everything else */
function sanitizeBotHTML(raw) {
// 1. Escape the entire string first
const esc = raw
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
// 2. Re-enable only safe <a> tags
return esc.replace(
/&lt;a\s+href=&quot;(https?:\/\/[^&"<>]+)&quot;(?:\s+target=&quot;(_blank)&quot;)?&gt;(.+?)&lt;\/a&gt;/gi,
(_, href, target, label) =>
`<a href="${href}" target="${target || '_blank'}" rel="noopener noreferrer">${label}</a>`
);
}
function appendBubble(type, text, actions) { function appendBubble(type, text, actions) {
const msgs = document.getElementById('chat-messages'); const msgs = document.getElementById('chat-messages');
if (!msgs) return; if (!msgs) return;
const div = document.createElement('div'); const div = document.createElement('div');
div.className = `chat-bubble chat-bubble--${type}`; div.className = `chat-bubble chat-bubble--${type}`;
div.setAttribute('role', 'article'); div.setAttribute('role', 'article');
div.textContent = text; // textContent only no XSS (#6) // Bot messages: render safe <a> links; user/error messages: plain text only
if (type === 'bot' || type === 'error') {
div.innerHTML = sanitizeBotHTML(text);
} else {
div.textContent = text;
}
if (actions?.length) { if (actions?.length) {
const row = document.createElement('div'); row.className = 'chat-fallback-actions'; const row = document.createElement('div'); row.className = 'chat-fallback-actions';

View File

@@ -565,6 +565,15 @@ button { cursor: pointer; font-family: inherit; border: none; background: none;
background: rgba(180,50,30,0.15); border: 1px solid rgba(180,50,30,0.3); background: rgba(180,50,30,0.15); border: 1px solid rgba(180,50,30,0.3);
color: #F5C0B0; border-bottom-left-radius: 3px; color: #F5C0B0; border-bottom-left-radius: 3px;
} }
.chat-bubble--bot a {
color: var(--c-accent-light, #F5A623);
text-decoration: underline;
text-underline-offset: 2px;
word-break: break-word;
}
.chat-bubble--bot a:hover {
color: var(--c-accent, #D4820A);
}
.chat-typing { .chat-typing {
align-self: flex-start; display: flex; gap: 4px; padding: 12px 14px; align-self: flex-start; display: flex; gap: 4px; padding: 12px 14px;
background: var(--c-surface); border: 1px solid var(--c-border); background: var(--c-surface); border: 1px solid var(--c-border);