diff --git a/src-ui/src/app/components/chat/chat/chat.component.spec.ts b/src-ui/src/app/components/chat/chat/chat.component.spec.ts index 67d8459ac..04948f3e9 100644 --- a/src-ui/src/app/components/chat/chat/chat.component.spec.ts +++ b/src-ui/src/app/components/chat/chat/chat.component.spec.ts @@ -221,4 +221,19 @@ describe('ChatComponent', () => { expect(el.querySelector('.chat-markdown script')).toBeNull() expect(el.textContent).toContain('**user**') }) + + it('should keep the destination hostname visible after sanitizing chat links', () => { + component.messages.set([ + { + role: 'assistant', + content: + '[View original document](https://otherdomain.example/?data=secret)', + }, + ]) + fixture.detectChanges() + const el: HTMLElement = fixture.nativeElement + const link = el.querySelector('.chat-markdown a') + expect(link?.textContent).toBe('View original document') + expect(link?.nextElementSibling?.textContent).toBe('(otherdomain.example)') + }) }) diff --git a/src-ui/src/app/pipes/markdown.pipe.spec.ts b/src-ui/src/app/pipes/markdown.pipe.spec.ts index 4dbfc719e..102e58065 100644 --- a/src-ui/src/app/pipes/markdown.pipe.spec.ts +++ b/src-ui/src/app/pipes/markdown.pipe.spec.ts @@ -31,9 +31,11 @@ describe('MarkdownPipe', () => { }) it('should not render images', () => { - const html = pipe.transform('![secret](https://evil.example/x.png?d=1)') + const html = pipe.transform( + '![secret](https://otherdomain.example/x.png?d=1)' + ) expect(html).not.toContain(' { } }) + it('should show the actual hostname beside web links with misleading labels', () => { + for (const protocol of ['http', 'https']) { + const html = pipe.transform( + `[docs.paperless-ngx.com](${protocol}://docs.paperless-ngx.com@otherdomain.example/path?secret=1)` + ) + expect(html).toContain( + ' (otherdomain.example)' + ) + expect(html).not.toContain('(docs.paperless-ngx.com)') + } + }) + + it('should omit the hostname label for mailto links', () => { + expect(pipe.transform('[mail](mailto:a@b.c)')).toBe( + '

mail

\n' + ) + }) + it('should escape link titles', () => { const html = pipe.transform( '[x](https://a.example "a\\" onmouseover=\\"1")' diff --git a/src-ui/src/app/pipes/markdown.pipe.ts b/src-ui/src/app/pipes/markdown.pipe.ts index 1b6c39274..fbfb1d0e6 100644 --- a/src-ui/src/app/pipes/markdown.pipe.ts +++ b/src-ui/src/app/pipes/markdown.pipe.ts @@ -35,7 +35,11 @@ const renderer: Partial = { const url = safeHref(href) if (!url) return text const titleAttr = title ? ` title="${escapeHtml(title)}"` : '' - return `${text}` + const hostname = new URL(url).hostname + const hostnameLabel = hostname + ? ` (${escapeHtml(hostname)})` + : '' + return `${text}${hostnameLabel}` }, }