Skip to content

Commit 1c2fefe

Browse files
authored
Fixed Link extension's commands not respecting XSS prevention via unallowed protocols (#5945)
* fixed link commands not respecting allowed protocols * added changesets * refactor(link): don't use throw for invalid uri handling
1 parent 7567ace commit 1c2fefe

3 files changed

Lines changed: 32 additions & 3 deletions

File tree

.changeset/empty-seals-join.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@tiptap/extension-link": patch
3+
---
4+
5+
Added checks for allowed protocols in link commands & exported isValidUri helper for manual checks outside of the extension

demos/src/Marks/Link/React/index.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ export default () => {
105105
}
106106

107107
// update link
108-
editor.chain().focus().extendMarkRange('link').setLink({ href: url })
109-
.run()
108+
try {
109+
editor.chain().focus().extendMarkRange('link').setLink({ href: url })
110+
.run()
111+
} catch (e) {
112+
alert(e.message)
113+
}
110114
}, [editor])
111115

112116
if (!editor) {

packages/extension-link/src/link.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ declare module '@tiptap/core' {
160160
// eslint-disable-next-line no-control-regex
161161
const ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g
162162

163-
function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {
163+
export function isAllowedUri(uri: string | undefined, protocols?: LinkOptions['protocols']) {
164164
const allowedProtocols: string[] = [
165165
'http',
166166
'https',
@@ -322,11 +322,31 @@ export const Link = Mark.create<LinkOptions>({
322322
return {
323323
setLink:
324324
attributes => ({ chain }) => {
325+
const { href } = attributes
326+
327+
if (!this.options.isAllowedUri(href, {
328+
defaultValidate: url => !!isAllowedUri(url, this.options.protocols),
329+
protocols: this.options.protocols,
330+
defaultProtocol: this.options.defaultProtocol,
331+
})) {
332+
return false
333+
}
334+
325335
return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run()
326336
},
327337

328338
toggleLink:
329339
attributes => ({ chain }) => {
340+
const { href } = attributes
341+
342+
if (!this.options.isAllowedUri(href, {
343+
defaultValidate: url => !!isAllowedUri(url, this.options.protocols),
344+
protocols: this.options.protocols,
345+
defaultProtocol: this.options.defaultProtocol,
346+
})) {
347+
return false
348+
}
349+
330350
return chain()
331351
.toggleMark(this.name, attributes, { extendEmptyMarkRange: true })
332352
.setMeta('preventAutolink', true)

0 commit comments

Comments
 (0)