2
This commit is contained in:
@@ -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, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"');
|
||||||
|
// 2. Re-enable only safe <a> tags
|
||||||
|
return esc.replace(
|
||||||
|
/<a\s+href="(https?:\/\/[^&"<>]+)"(?:\s+target="(_blank)")?>(.+?)<\/a>/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';
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user