diff --git a/app/javascript/elements/html_editor.js b/app/javascript/elements/html_editor.js
index 8c9a2d2c..52c1c4c6 100644
--- a/app/javascript/elements/html_editor.js
+++ b/app/javascript/elements/html_editor.js
@@ -106,11 +106,29 @@ img.ProseMirror-separator {
}
`)
+const DROP_ATTRS = [
+ 'srcdoc', 'xlink:href', 'srcset', 'action', 'formaction', 'poster',
+ 'background', 'data', 'cite', 'ping', 'longdesc', 'manifest', 'profile'
+]
+
+const SAFE_URL_REGEXP = /^(?:https?:\/\/|data:image\/|blob:|mailto:|tel:|\{)/i
+
+function isSafeAttr (name, value) {
+ const lowerName = name.toLowerCase()
+
+ if (lowerName.startsWith('on') || DROP_ATTRS.includes(lowerName)) return false
+ if ((lowerName === 'href' || lowerName === 'src') && !SAFE_URL_REGEXP.test(value.trim())) return false
+
+ return true
+}
+
function collectDomAttrs (dom) {
const attrs = {}
for (let i = 0; i < dom.attributes.length; i++) {
- attrs[dom.attributes[i].name] = dom.attributes[i].value
+ const { name, value } = dom.attributes[i]
+
+ if (isSafeAttr(name, value)) attrs[name] = value
}
return { htmlAttrs: attrs }
diff --git a/app/javascript/template_builder/dynamic_editor.js b/app/javascript/template_builder/dynamic_editor.js
index d83062a4..580d47ad 100644
--- a/app/javascript/template_builder/dynamic_editor.js
+++ b/app/javascript/template_builder/dynamic_editor.js
@@ -98,11 +98,29 @@ dynamic-variable {
overflow-wrap: anywhere;
}`)
+const DROP_ATTRS = [
+ 'srcdoc', 'xlink:href', 'srcset', 'action', 'formaction', 'poster',
+ 'background', 'data', 'cite', 'ping', 'longdesc', 'manifest', 'profile'
+]
+
+const SAFE_URL_REGEXP = /^(?:https?:\/\/|data:image\/|blob:|mailto:|tel:|\{)/i
+
+function isSafeAttr (name, value) {
+ const lowerName = name.toLowerCase()
+
+ if (lowerName.startsWith('on') || DROP_ATTRS.includes(lowerName)) return false
+ if ((lowerName === 'href' || lowerName === 'src') && !SAFE_URL_REGEXP.test(value.trim())) return false
+
+ return true
+}
+
function collectDomAttrs (dom) {
const attrs = {}
for (let i = 0; i < dom.attributes.length; i++) {
- attrs[dom.attributes[i].name] = dom.attributes[i].value
+ const { name, value } = dom.attributes[i]
+
+ if (isSafeAttr(name, value)) attrs[name] = value
}
return { htmlAttrs: attrs }
diff --git a/app/javascript/template_builder/dynamic_section.vue b/app/javascript/template_builder/dynamic_section.vue
index 9fd81246..852a7a56 100644
--- a/app/javascript/template_builder/dynamic_section.vue
+++ b/app/javascript/template_builder/dynamic_section.vue
@@ -646,9 +646,7 @@ export default {
return
}
- const container = document.createElement('div')
-
- container.innerHTML = clipboardHtml
+ const container = new DOMParser().parseFromString(clipboardHtml, 'text/html').body
const fieldNodes = [...container.querySelectorAll('dynamic-field[data-field][data-area]')]