Skip to content

Commit 249be95

Browse files
committed
test(file-viewer): lock link href sanitization for dangerous schemes from file content
Greptile flagged a possible javascript: link XSS. Verified TipTap 3.26.1 already neutralizes javascript:/data:/vbscript: (and mixed-case/whitespace variants) from file-loaded markdown to an empty href. Add a committed regression test that asserts this against the real headless editor, so a future TipTap bump can't silently reintroduce the issue.
1 parent 6e8bd21 commit 249be95

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

  • apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/round-trip.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,39 @@ describe('editor markdown round-trip', () => {
266266
expect(roundTrip(out)).toBe(out)
267267
})
268268
})
269+
270+
/**
271+
* Links come from arbitrary file content (a README the editor opens, agent-written markdown), not
272+
* just user-typed text — so the rendered anchor must never carry a dangerous scheme. This locks the
273+
* guarantee in our own test rather than trusting a transitive TipTap default to keep neutralizing
274+
* `javascript:`/`data:`/`vbscript:` across version bumps.
275+
*/
276+
describe('link href sanitization — dangerous schemes from file content are neutralized', () => {
277+
function renderedHrefs(markdown: string): Array<string | null> {
278+
editor = new Editor({ extensions: createMarkdownContentExtensions() })
279+
editor.commands.setContent(markdown, { contentType: 'markdown' })
280+
const html = editor.getHTML()
281+
editor.destroy()
282+
editor = null
283+
const doc = new DOMParser().parseFromString(html, 'text/html')
284+
return Array.from(doc.querySelectorAll('a')).map((a) => a.getAttribute('href'))
285+
}
286+
287+
it.each([
288+
'javascript:alert(document.cookie)',
289+
'JaVaScRiPt:alert(1)',
290+
' javascript:alert(1)',
291+
'data:text/html,<script>alert(1)</script>',
292+
'vbscript:msgbox(1)',
293+
])('does not render %s as a clickable href', (scheme) => {
294+
for (const href of renderedHrefs(`[click me](${scheme})`)) {
295+
expect((href ?? '').replace(/\s/g, '')).not.toMatch(/^(javascript|data|vbscript):/i)
296+
}
297+
})
298+
299+
it('preserves safe http/https/mailto links', () => {
300+
const hrefs = renderedHrefs('[a](https://ok.example.com)\n\n[b](mailto:x@y.com)')
301+
expect(hrefs).toContain('https://ok.example.com')
302+
expect(hrefs).toContain('mailto:x@y.com')
303+
})
304+
})

0 commit comments

Comments
 (0)