/** * Inline rendering: the security model and the link bugs it hid. * * Escape-then-format is the whole defence, and it has one awkward consequence * that produced real broken links: by the time a URL is captured it is already * escaped, so it has to be decoded before it can be escaped again exactly once. */ import { describe, it, expect } from 'vitest' import { renderInline } from './inline.js' describe('renderInline', () => { it('escapes markup an author pasted', () => { expect(renderInline('')) .toBe('<img src=x onerror=alert(1)>') }) it('keeps a multi-parameter URL intact', () => { // Double-escaped, this became &amp; and the link opened the wrong // record. Every ServiceNow and SharePoint link carries several parameters. const html = renderInline('[ticket](https://x.example/nav?a=1&b=2&c=3)') expect(html).toContain('href="https://x.example/nav?a=1&b=2&c=3"') expect(html).not.toContain('&amp;') }) it('does not let emphasis reach inside an href', () => { const html = renderInline('[t](https://x.example/a*b*c)') expect(html).toContain('href="https://x.example/a*b*c"') expect(html).not.toContain('') }) it('refuses a protocol-relative URL dressed as an in-app path', () => { // '//evil.example' starts with '/' but the browser reads it as off-site. expect(renderInline('[evil](//evil.example/x)')).toBe('evil') }) it('refuses javascript: and keeps the label', () => { expect(renderInline('[bad](javascript:alert(1))')).toBe('bad') }) it('allows an in-app link', () => { expect(renderInline('[map](/map)')).toBe('map') }) it('still renders bold, italic and code', () => { expect(renderInline('**b**')).toBe('b') expect(renderInline('*i*')).toBe('i') expect(renderInline('`C:\\**temp**`')) .toBe('C:\\**temp**') }) })