diff --git a/CHANGELOG.md b/CHANGELOG.md index 86e66bd907..3fca632b72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ Docs: https://docs.openclaw.ai - Config: keep legacy audio transcription migration strict by rejecting non-string/unsafe command tokens while still migrating valid custom script executables. (#5042) Thanks @shayan919293. - Status/Sessions: stop clamping derived `totalTokens` to context-window size, keep prompt-token snapshots wired through session accounting, and surface context usage as unknown when fresh snapshot data is missing to avoid false 100% reports. (#15114) Thanks @echoVic. - Providers/MiniMax: switch implicit MiniMax API-key provider from `openai-completions` to `anthropic-messages` with the correct Anthropic-compatible base URL, fixing `invalid role: developer (2013)` errors on MiniMax M2.5. (#15275) Thanks @lailoo. +- Web UI: add `img` to DOMPurify allowed tags and `src`/`alt` to allowed attributes so markdown images render in webchat instead of being stripped. (#15437) Thanks @lailoo. - Ollama/Agents: use resolved model/provider base URLs for native `/api/chat` streaming (including aliased providers), normalize `/v1` endpoints, and forward abort + `maxTokens` stream options for reliable cancellation and token caps. (#11853) Thanks @BrokenFinger98. ## 2026.2.12 diff --git a/ui/src/ui/markdown.test.ts b/ui/src/ui/markdown.test.ts index daf628ef88..9b486f1bec 100644 --- a/ui/src/ui/markdown.test.ts +++ b/ui/src/ui/markdown.test.ts @@ -28,4 +28,24 @@ describe("toSanitizedMarkdownHtml", () => { expect(html).toContain(" { + const html = toSanitizedMarkdownHtml("![Alt text](https://example.com/image.png)"); + expect(html).toContain(" { + const html = toSanitizedMarkdownHtml("![Chart](data:image/png;base64,iVBORw0KGgo=)"); + expect(html).toContain(" { + const html = toSanitizedMarkdownHtml("![X](javascript:alert(1))"); + expect(html).toContain(" MARKDOWN_PARSE_LIMIT) { const escaped = escapeHtml(`${truncated.text}${suffix}`); const html = `
${escaped}
`; - const sanitized = DOMPurify.sanitize(html, { - ALLOWED_TAGS: allowedTags, - ALLOWED_ATTR: allowedAttrs, - }); + const sanitized = DOMPurify.sanitize(html, sanitizeOptions); if (input.length <= MARKDOWN_CACHE_MAX_CHARS) { setCachedMarkdown(input, sanitized); } @@ -115,10 +118,7 @@ export function toSanitizedMarkdownHtml(markdown: string): string { const rendered = marked.parse(`${truncated.text}${suffix}`, { renderer: htmlEscapeRenderer, }) as string; - const sanitized = DOMPurify.sanitize(rendered, { - ALLOWED_TAGS: allowedTags, - ALLOWED_ATTR: allowedAttrs, - }); + const sanitized = DOMPurify.sanitize(rendered, sanitizeOptions); if (input.length <= MARKDOWN_CACHE_MAX_CHARS) { setCachedMarkdown(input, sanitized); }