diff --git a/apps/activities/_day.html b/apps/activities/_day.html index a0ae68f17..c23ea53fc 100644 --- a/apps/activities/_day.html +++ b/apps/activities/_day.html @@ -583,6 +583,10 @@ button.activity-detail-back { return d.innerHTML; } + function renderMarkdown(raw) { + return DOMPurify.sanitize(marked.parse(raw || '', { breaks: true, gfm: true })); + } + function fmtTime(ts) { const d = new Date(ts); let h = d.getHours(); @@ -857,7 +861,7 @@ button.activity-detail-back { let rendered; if (data.format === 'md') { - rendered = `
${marked.parse(data.content, { breaks: true, gfm: true })}
`; + rendered = `
${renderMarkdown(data.content)}
`; } else if (data.format === 'json') { let pretty; try { pretty = JSON.stringify(JSON.parse(data.content), null, 2); } catch (e) { pretty = data.content; } diff --git a/apps/home/workspace.html b/apps/home/workspace.html index 901dfb01a..d6c8e9038 100644 --- a/apps/home/workspace.html +++ b/apps/home/workspace.html @@ -1341,9 +1341,14 @@ var briefingSections = {{ briefing_sections|tojson|safe }}; var skillContent = {{ skills_content|default({})|tojson|safe }}; var sectionOrder = ['your_day', 'yesterday', 'forward_look', 'reading']; + + function renderMarkdown(raw) { + return DOMPurify.sanitize(marked.parse(raw || '', { breaks: true, gfm: true })); + } + if (narrativeRaw) { const el = document.getElementById('pulse-narrative-content'); - if (el) el.innerHTML = marked.parse(narrativeRaw, {breaks: true, gfm: true}); + if (el) el.innerHTML = renderMarkdown(narrativeRaw); } function renderBriefingSections(sections) { @@ -1355,7 +1360,7 @@ el.innerHTML = ''; return; } - el.innerHTML = marked.parse(raw, {breaks: true, gfm: true}); + el.innerHTML = renderMarkdown(raw); if (key === 'reading') { el.querySelectorAll('li').forEach(function(li) { var strong = li.querySelector('strong'); @@ -1408,14 +1413,14 @@ renderBriefingSections(briefingSections); function toggleSkillDetail(skillId) { - var detail = document.getElementById('skill-detail-' + skillId); - if (!detail) return; - if (detail.style.display === 'none') { - if (skillContent[skillId]) { - detail.innerHTML = marked.parse(skillContent[skillId], {breaks: true, gfm: true}); - } - detail.style.display = 'block'; - } else { + var detail = document.getElementById('skill-detail-' + skillId); + if (!detail) return; + if (detail.style.display === 'none') { + if (skillContent[skillId]) { + detail.innerHTML = renderMarkdown(skillContent[skillId]); + } + detail.style.display = 'block'; + } else { detail.style.display = 'none'; } } @@ -1613,7 +1618,7 @@ .then(data => { const el = document.getElementById('pulse-narrative-content'); if (el && data.narrative_content) { - el.innerHTML = marked.parse(data.narrative_content, {breaks: true, gfm: true}); + el.innerHTML = renderMarkdown(data.narrative_content); const hdr = document.querySelector('.pulse-narrative .pulse-section-header'); if (hdr && data.narrative_header) hdr.textContent = data.narrative_header; const meta = document.querySelector('.pulse-narrative .pulse-narrative-meta'); diff --git a/apps/import/_detail.html b/apps/import/_detail.html index 09a3085ad..1b7faba63 100644 --- a/apps/import/_detail.html +++ b/apps/import/_detail.html @@ -166,19 +166,7 @@ - -
{% if view_mode == "index" %}
diff --git a/apps/sol/workspace.html b/apps/sol/workspace.html index 862e56a86..67e0c37ff 100644 --- a/apps/sol/workspace.html +++ b/apps/sol/workspace.html @@ -2088,7 +2088,7 @@ if (data.format === 'md') { const markdown = document.createElement('div'); markdown.className = 'rendered-markdown'; - markdown.innerHTML = sanitizeHtml(marked.parse(data.content, { breaks: true, gfm: true })); + markdown.innerHTML = renderMarkdown(data.content); container.appendChild(markdown); } else { const pre = document.createElement('pre'); @@ -2128,42 +2128,8 @@ return div.innerHTML; } - // Sanitize HTML to allow only safe tags and attributes - function sanitizeHtml(html) { - const doc = new DOMParser().parseFromString(html, 'text/html'); - const allowedTags = new Set([ - 'p', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li', 'a', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'br', 'hr', - 'table', 'thead', 'tbody', 'tr', 'th', 'td' - ]); - const allowedAttributes = { - a: new Set(['href']) - }; - - Array.from(doc.body.querySelectorAll('*')).forEach(function(el) { - const tagName = el.tagName.toLowerCase(); - if (!allowedTags.has(tagName)) { - el.replaceWith(doc.createTextNode(el.textContent || '')); - return; - } - - Array.from(el.attributes).forEach(function(attr) { - if (!allowedAttributes[tagName] || !allowedAttributes[tagName].has(attr.name)) { - el.removeAttribute(attr.name); - } - }); - - if (tagName === 'a') { - const href = el.getAttribute('href'); - if (!href || !/^(https?:\/\/|mailto:)/.test(href)) { - el.removeAttribute('href'); - } - el.setAttribute('target', '_blank'); - el.setAttribute('rel', 'noopener noreferrer'); - } - }); - - return doc.body.innerHTML; + function renderMarkdown(raw) { + return DOMPurify.sanitize(marked.parse(raw || '', { breaks: true, gfm: true })); } // Render agent events as a rich interactive timeline @@ -2348,7 +2314,7 @@ if (event.result && event.result.trim()) { const resultDiv = document.createElement('div'); resultDiv.className = 'finish-result'; - resultDiv.innerHTML = sanitizeHtml(marked.parse(event.result, { breaks: true, gfm: true })); + resultDiv.innerHTML = renderMarkdown(event.result); block.appendChild(resultDiv); } else { const empty = document.createElement('div'); diff --git a/apps/transcripts/workspace.html b/apps/transcripts/workspace.html index a947d81dd..193c8cea5 100644 --- a/apps/transcripts/workspace.html +++ b/apps/transcripts/workspace.html @@ -1291,7 +1291,6 @@ body.has-date-nav .workspace:has(.tr-wrap) {
- +