Skip to content

Commit 180c8d3

Browse files
authored
Merge commit from fork
Fix XSS via `javascript:` url in a link
2 parents f4d64c2 + f432478 commit 180c8d3

File tree

3 files changed

+27
-2
lines changed

3 files changed

+27
-2
lines changed

src/test/system/text_formatting_test.js

+12
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ testGroup("Text formatting", { template: "editor_empty" }, () => {
5858
expectDocument("ahttp://example.com\n")
5959
})
6060

61+
test("inserting a javascript: link is forbidden", async () => {
62+
await typeCharacters("XSS")
63+
await moveCursor("left")
64+
await expandSelection("left")
65+
await clickToolbarButton({ attribute: "href" })
66+
assert.ok(isToolbarDialogActive({ attribute: "href" }))
67+
await typeInToolbarDialog("javascript:alert('XSS')", { attribute: "href" })
68+
assert.textAttributes([ 0, 1 ], {})
69+
assert.textAttributes([ 1, 2 ], { frozen: true })
70+
assert.textAttributes([ 2, 3 ], {})
71+
})
72+
6173
test("editing a link", async () => {
6274
insertString("a")
6375
const text = Text.textForStringWithAttributes("bc", { href: "http://example.com" })

src/trix/config/toolbar.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default {
3535
<div class="trix-dialogs" data-trix-dialogs>
3636
<div class="trix-dialog trix-dialog--link" data-trix-dialog="href" data-trix-dialog-attribute="href">
3737
<div class="trix-dialog__link-fields">
38-
<input type="url" name="href" class="trix-input trix-input--dialog" placeholder="${lang.urlPlaceholder}" aria-label="${lang.url}" required data-trix-input>
38+
<input type="url" name="href" class="trix-input trix-input--dialog" placeholder="${lang.urlPlaceholder}" aria-label="${lang.url}" data-trix-validate-href required data-trix-input>
3939
<div class="trix-button-group">
4040
<input type="button" class="trix-button trix-button--dialog" value="${lang.link}" data-trix-method="setAttribute">
4141
<input type="button" class="trix-button trix-button--dialog" value="${lang.unlink}" data-trix-method="removeAttribute">

src/trix/controllers/toolbar_controller.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import BasicObject from "trix/core/basic_object"
22

33
import { findClosestElementFromNode, handleEvent, triggerEvent } from "trix/core/helpers"
44

5+
import DOMPurify from "dompurify"
6+
57
const attributeButtonSelector = "[data-trix-attribute]"
68
const actionButtonSelector = "[data-trix-action]"
79
const toolbarButtonSelector = `${attributeButtonSelector}, ${actionButtonSelector}`
@@ -205,7 +207,10 @@ export default class ToolbarController extends BasicObject {
205207
setAttribute(dialogElement) {
206208
const attributeName = getAttributeName(dialogElement)
207209
const input = getInputForDialog(dialogElement, attributeName)
208-
if (input.willValidate && !input.checkValidity()) {
210+
211+
input.willValidate && input.setCustomValidity("")
212+
if (input.willValidate && !input.checkValidity() || !this.safeAttribute(input)) {
213+
input.setCustomValidity("Invalid value")
209214
input.setAttribute("data-trix-validate", "")
210215
input.classList.add("trix-validate")
211216
return input.focus()
@@ -215,6 +220,14 @@ export default class ToolbarController extends BasicObject {
215220
}
216221
}
217222

223+
safeAttribute(input) {
224+
if (input.hasAttribute("data-trix-validate-href")) {
225+
return DOMPurify.isValidAttribute("a", "href", input.value)
226+
} else {
227+
return true
228+
}
229+
}
230+
218231
removeAttribute(dialogElement) {
219232
const attributeName = getAttributeName(dialogElement)
220233
this.delegate?.toolbarDidRemoveAttribute(attributeName)

0 commit comments

Comments
 (0)