From c93e33c17709123fb289971ed49fa2e2ffd2055a Mon Sep 17 00:00:00 2001
From: Csaba Tuncsik
Date: Fri, 13 Sep 2024 06:54:35 +0200
Subject: [PATCH] fix(editor): Replace v-html with custom directive to sanitize
content
---
.../AskAssistantChat/AskAssistantChat.vue | 8 +++---
.../src/components/N8nActionBox/ActionBox.vue | 4 +--
.../N8nInfoAccordion/InfoAccordion.vue | 4 +--
.../components/N8nInputLabel/InputLabel.vue | 2 +-
.../src/components/N8nMarkdown/Markdown.vue | 2 +-
.../src/components/N8nNotice/Notice.vue | 2 +-
.../src/components/N8nSticky/Sticky.vue | 2 +-
.../src/components/N8nTabs/Tabs.vue | 2 +-
.../src/components/N8nTooltip/Tooltip.vue | 2 +-
.../design-system/src/directives/index.ts | 1 +
.../src/directives/n8n-html.test.ts | 28 +++++++++++++++++++
.../design-system/src/directives/n8n-html.ts | 15 ++++++++++
packages/design-system/src/main.ts | 1 +
packages/design-system/src/plugin.ts | 5 ++++
.../src/components/Error/NodeErrorView.vue | 2 +-
.../src/components/ExpressionEditModal.vue | 2 +-
.../src/components/FeatureComingSoon.vue | 4 +--
.../InlineExpressionTip.vue | 6 ++--
.../editor-ui/src/components/InputPanel.vue | 2 +-
.../editor-ui/src/components/MappingPill.vue | 2 +-
packages/editor-ui/src/components/Node.vue | 2 +-
.../Node/NodeCreator/ItemTypes/NodeItem.vue | 2 +-
.../Node/NodeCreator/Modes/ActionsMode.vue | 12 ++++----
.../Renderers/CategorizedItemsRenderer.vue | 2 +-
.../src/components/ParameterInput.vue | 4 +--
.../src/components/ParameterInputHint.vue | 4 +--
.../src/components/PushConnectionTracker.vue | 2 +-
.../ResourceMapper/MappingModeSelect.vue | 2 +-
packages/editor-ui/src/components/RunData.vue | 4 +--
packages/editor-ui/src/components/RunInfo.vue | 2 +-
.../editor-ui/src/components/TitledList.vue | 2 +-
.../editor-ui/src/components/TriggerPanel.vue | 2 +-
.../editor-ui/src/components/VersionCard.vue | 4 +--
.../src/components/WorkflowActivator.vue | 2 +-
.../src/components/WorkflowSettings.vue | 2 +-
.../src/components/banners/V1Banner.vue | 4 +--
.../nodes/render-types/CanvasNodeDefault.vue | 2 +-
.../WorkflowExecutionAnnotationPanel.vue | 2 +-
.../editor-ui/src/views/SettingsLdapView.vue | 2 +-
.../src/views/SettingsLogStreamingView.vue | 8 +++---
.../AppsRequiringCredsNotice.vue | 2 +-
.../SetupTemplateFormStep.vue | 2 +-
.../src/views/TemplatesSearchView.vue | 2 +-
43 files changed, 109 insertions(+), 59 deletions(-)
create mode 100644 packages/design-system/src/directives/index.ts
create mode 100644 packages/design-system/src/directives/n8n-html.test.ts
create mode 100644 packages/design-system/src/directives/n8n-html.ts
diff --git a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue
index 1fb5d42c63a58..88fc15b902d2b 100644
--- a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue
+++ b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue
@@ -152,7 +152,7 @@ function growInput() {
-
+
@@ -161,17 +161,17 @@ function growInput() {
-
+
(), {
-
+
@@ -61,7 +61,7 @@ withDefaults(defineProps(), {
:class="$style.callout"
>
-
+
diff --git a/packages/design-system/src/components/N8nInfoAccordion/InfoAccordion.vue b/packages/design-system/src/components/N8nInfoAccordion/InfoAccordion.vue
index 71830b02dcac6..e500c73cd65f1 100644
--- a/packages/design-system/src/components/N8nInfoAccordion/InfoAccordion.vue
+++ b/packages/design-system/src/components/N8nInfoAccordion/InfoAccordion.vue
@@ -75,7 +75,7 @@ const onTooltipClick = (item: string, event: MouseEvent) => emit('tooltipClick',
-
+
@@ -83,7 +83,7 @@ const onTooltipClick = (item: string, event: MouseEvent) => emit('tooltipClick',
-
+
diff --git a/packages/design-system/src/components/N8nInputLabel/InputLabel.vue b/packages/design-system/src/components/N8nInputLabel/InputLabel.vue
index 6c527549ed0f3..bda96c2e21feb 100644
--- a/packages/design-system/src/components/N8nInputLabel/InputLabel.vue
+++ b/packages/design-system/src/components/N8nInputLabel/InputLabel.vue
@@ -58,7 +58,7 @@ const addTargetBlank = (html: string) =>
-
+
diff --git a/packages/design-system/src/components/N8nMarkdown/Markdown.vue b/packages/design-system/src/components/N8nMarkdown/Markdown.vue
index c953ff9d657e5..45de891020bb3 100644
--- a/packages/design-system/src/components/N8nMarkdown/Markdown.vue
+++ b/packages/design-system/src/components/N8nMarkdown/Markdown.vue
@@ -202,7 +202,7 @@ const onCheckboxChange = (index: number) => {
@click="onClick"
@mousedown="onMouseDown"
@change="onChange"
- v-html="htmlContent"
+ v-n8n-html="htmlContent"
/>
diff --git a/packages/design-system/src/components/N8nNotice/Notice.vue b/packages/design-system/src/components/N8nNotice/Notice.vue
index eb27aa2efdf8f..f630e95e59c77 100644
--- a/packages/design-system/src/components/N8nNotice/Notice.vue
+++ b/packages/design-system/src/components/N8nNotice/Notice.vue
@@ -73,7 +73,7 @@ const onClick = (event: MouseEvent) => {
:id="`${id}-content`"
:class="showFullContent ? $style['expanded'] : $style['truncated']"
role="region"
- v-html="displayContent"
+ v-n8n-html="displayContent"
/>
diff --git a/packages/design-system/src/components/N8nSticky/Sticky.vue b/packages/design-system/src/components/N8nSticky/Sticky.vue
index 4b7dca944acb0..9e258b4afa75a 100644
--- a/packages/design-system/src/components/N8nSticky/Sticky.vue
+++ b/packages/design-system/src/components/N8nSticky/Sticky.vue
@@ -116,7 +116,7 @@ const onInputScroll = (event: WheelEvent) => {
-
+
diff --git a/packages/design-system/src/components/N8nTabs/Tabs.vue b/packages/design-system/src/components/N8nTabs/Tabs.vue
index 64277ca8630e9..ff1b142e516e2 100644
--- a/packages/design-system/src/components/N8nTabs/Tabs.vue
+++ b/packages/design-system/src/components/N8nTabs/Tabs.vue
@@ -89,7 +89,7 @@ const scrollRight = () => scroll(50);
>
-
+
-
+
text
malicious![Ok](./images/logo.svg)
',
+ };
+ },
+ template: '
',
+});
+
+describe('Directive n8n-html', () => {
+ it('should sanitize html', async () => {
+ const { html } = render(TestComponent, {
+ global: {
+ directives: {
+ 'n8n-html': n8nHtml,
+ },
+ },
+ });
+ expect(html()).toBe(
+ '
',
+ );
+ });
+});
diff --git a/packages/design-system/src/directives/n8n-html.ts b/packages/design-system/src/directives/n8n-html.ts
new file mode 100644
index 0000000000000..763ed61ce8578
--- /dev/null
+++ b/packages/design-system/src/directives/n8n-html.ts
@@ -0,0 +1,15 @@
+import sanitize from 'sanitize-html';
+import type { DirectiveBinding, ObjectDirective } from 'vue';
+
+const configuredSanitize = (html: string) => sanitize(html, {
+ allowedTags: sanitize.defaults.allowedTags.concat([ 'img' ])
+});
+
+export const n8nHtml: ObjectDirective = {
+ beforeMount(el: HTMLElement, binding: DirectiveBinding) {
+ el.innerHTML = configuredSanitize(binding.value);
+ },
+ beforeUpdate(el: HTMLElement, binding: DirectiveBinding) {
+ el.innerHTML = configuredSanitize(binding.value);
+ },
+};
diff --git a/packages/design-system/src/main.ts b/packages/design-system/src/main.ts
index 54934d6f1c4d6..dddb89f888ff4 100644
--- a/packages/design-system/src/main.ts
+++ b/packages/design-system/src/main.ts
@@ -5,4 +5,5 @@ export * from './components';
export * from './plugin';
export * from './types';
export * from './utils';
+export * from './directives';
export { locale };
diff --git a/packages/design-system/src/plugin.ts b/packages/design-system/src/plugin.ts
index 493fde38ecc5a..c6d3d2a6c9f30 100644
--- a/packages/design-system/src/plugin.ts
+++ b/packages/design-system/src/plugin.ts
@@ -1,5 +1,6 @@
import type { Component, Plugin } from 'vue';
import * as components from './components';
+import * as directives from './directives';
export interface N8nPluginOptions {}
@@ -8,5 +9,9 @@ export const N8nPlugin: Plugin
= {
for (const [name, component] of Object.entries(components)) {
app.component(name, component as unknown as Component);
}
+
+ for (const [name, directive] of Object.entries(directives)) {
+ app.directive(name, directive);
+ }
},
};
diff --git a/packages/editor-ui/src/components/Error/NodeErrorView.vue b/packages/editor-ui/src/components/Error/NodeErrorView.vue
index 0bc3bf5387d03..aa0eac4c69ff0 100644
--- a/packages/editor-ui/src/components/Error/NodeErrorView.vue
+++ b/packages/editor-ui/src/components/Error/NodeErrorView.vue
@@ -445,7 +445,7 @@ async function onAskAssistantClick() {
v-if="error.description || error.context?.descriptionKey"
data-test-id="node-error-description"
class="node-error-view__header-description"
- v-html="getErrorDescription()"
+ v-n8n-html="getErrorDescription()"
>
diff --git a/packages/editor-ui/src/components/FeatureComingSoon.vue b/packages/editor-ui/src/components/FeatureComingSoon.vue
index a96b3d0ec31f8..fd24a35196b49 100644
--- a/packages/editor-ui/src/components/FeatureComingSoon.vue
+++ b/packages/editor-ui/src/components/FeatureComingSoon.vue
@@ -56,7 +56,7 @@ export default defineComponent({
-
+
@@ -68,7 +68,7 @@ export default defineComponent({
@click:button="openLinkPage"
>
-
+
diff --git a/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionTip.vue b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionTip.vue
index 9e0744025c26b..73f12a0ec3852 100644
--- a/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionTip.vue
+++ b/packages/editor-ui/src/components/InlineExpressionEditor/InlineExpressionTip.vue
@@ -115,15 +115,15 @@ watchDebounced(
-
+
-
+
-
+
diff --git a/packages/editor-ui/src/components/InputPanel.vue b/packages/editor-ui/src/components/InputPanel.vue
index 7bc38c811ed56..a9a6dd3909c7f 100644
--- a/packages/editor-ui/src/components/InputPanel.vue
+++ b/packages/editor-ui/src/components/InputPanel.vue
@@ -406,7 +406,7 @@ export default defineComponent({
(), {
diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue
index 83efe3b0a718d..56ca2cecf82b9 100644
--- a/packages/editor-ui/src/components/Node.vue
+++ b/packages/editor-ui/src/components/Node.vue
@@ -633,7 +633,7 @@ function openContextMenu(event: MouseEvent, source: 'node-button' | 'node-right-
-
+
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/ItemTypes/NodeItem.vue b/packages/editor-ui/src/components/Node/NodeCreator/ItemTypes/NodeItem.vue
index ebea3cba41979..49916381762a2 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/ItemTypes/NodeItem.vue
+++ b/packages/editor-ui/src/components/Node/NodeCreator/ItemTypes/NodeItem.vue
@@ -163,7 +163,7 @@ function onCommunityNodeTooltipClick(event: MouseEvent) {
{
data-test-id="actions-panel-no-triggers-callout"
>
{
@@ -293,13 +293,13 @@ onMounted(() => {
slim
data-test-id="actions-panel-activation-callout"
>
-
+
{
:class="$style.resetSearch"
data-test-id="actions-panel-no-matching-actions"
@click="resetSearch"
- v-html="i18n.baseText('nodeCreator.actionsCategory.noMatchingActions')"
+ v-n8n-html="i18n.baseText('nodeCreator.actionsCategory.noMatchingActions')"
/>
@@ -320,7 +320,7 @@ onMounted(() => {
-
+
diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue
index b5ca14dd887fd..df63a8b3228f4 100644
--- a/packages/editor-ui/src/components/ParameterInput.vue
+++ b/packages/editor-ui/src/components/ParameterInput.vue
@@ -1391,7 +1391,7 @@ onUpdated(async () => {
@@ -1424,7 +1424,7 @@ onUpdated(async () => {
diff --git a/packages/editor-ui/src/components/ParameterInputHint.vue b/packages/editor-ui/src/components/ParameterInputHint.vue
index 87ce1a92ab992..1c28e4e44a937 100644
--- a/packages/editor-ui/src/components/ParameterInputHint.vue
+++ b/packages/editor-ui/src/components/ParameterInputHint.vue
@@ -46,13 +46,13 @@ const simplyText = computed(() => {
[$style.highlight]: highlight,
}"
>
-
+
diff --git a/packages/editor-ui/src/components/PushConnectionTracker.vue b/packages/editor-ui/src/components/PushConnectionTracker.vue
index bd4ecc865494b..1be8fb5b6aaef 100644
--- a/packages/editor-ui/src/components/PushConnectionTracker.vue
+++ b/packages/editor-ui/src/components/PushConnectionTracker.vue
@@ -16,7 +16,7 @@ export default defineComponent({
-
+
diff --git a/packages/editor-ui/src/components/ResourceMapper/MappingModeSelect.vue b/packages/editor-ui/src/components/ResourceMapper/MappingModeSelect.vue
index 492c74e3b7f78..c605a97e6a394 100644
--- a/packages/editor-ui/src/components/ResourceMapper/MappingModeSelect.vue
+++ b/packages/editor-ui/src/components/ResourceMapper/MappingModeSelect.vue
@@ -128,7 +128,7 @@ defineExpose({
{{ option.name }}
-
+
diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue
index f85800fa160f1..348e3a455b9db 100644
--- a/packages/editor-ui/src/components/RunData.vue
+++ b/packages/editor-ui/src/components/RunData.vue
@@ -1350,7 +1350,7 @@ export default defineComponent({
:class="$style.hintCallout"
:theme="hint.type || 'info'"
>
-
+
{{ tooMuchDataTitle }}
{
data-test-id="node-run-info-stale"
>
diff --git a/packages/editor-ui/src/components/TriggerPanel.vue b/packages/editor-ui/src/components/TriggerPanel.vue
index db8a64736a2df..0702f34c49979 100644
--- a/packages/editor-ui/src/components/TriggerPanel.vue
+++ b/packages/editor-ui/src/components/TriggerPanel.vue
@@ -441,7 +441,7 @@ export default defineComponent({
-
+
{
{{ `${$locale.baseText('versionCard.version')} ${version.name}` }}
-
+
{
diff --git a/packages/editor-ui/src/components/WorkflowSettings.vue b/packages/editor-ui/src/components/WorkflowSettings.vue
index 2d915611ff07a..1ecac538a6a70 100644
--- a/packages/editor-ui/src/components/WorkflowSettings.vue
+++ b/packages/editor-ui/src/components/WorkflowSettings.vue
@@ -573,7 +573,7 @@ export default defineComponent({
{{ $locale.baseText('workflowSettings.errorWorkflow') + ':' }}
-
+
diff --git a/packages/editor-ui/src/components/banners/V1Banner.vue b/packages/editor-ui/src/components/banners/V1Banner.vue
index 72077f1a3c55c..6ea90c712c148 100644
--- a/packages/editor-ui/src/components/banners/V1Banner.vue
+++ b/packages/editor-ui/src/components/banners/V1Banner.vue
@@ -17,14 +17,14 @@ const hasOwnerPermission = computed(() => hasPermission(['instanceOwner']));
-
+
-
+
diff --git a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue b/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue
index 85d8dedad7153..bc443c1732027 100644
--- a/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue
+++ b/packages/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeDefault.vue
@@ -96,7 +96,7 @@ function openContextMenu(event: MouseEvent) {
-
+
diff --git a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationPanel.vue b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationPanel.vue
index 1ae4ef0e5c4df..437cfd8e12f7f 100644
--- a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationPanel.vue
+++ b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationPanel.vue
@@ -179,7 +179,7 @@ const onTagsEditEsc = () => {
-
+
diff --git a/packages/editor-ui/src/views/SettingsLdapView.vue b/packages/editor-ui/src/views/SettingsLdapView.vue
index 8feca49da78c4..6cab6b1ba54d5 100644
--- a/packages/editor-ui/src/views/SettingsLdapView.vue
+++ b/packages/editor-ui/src/views/SettingsLdapView.vue
@@ -633,7 +633,7 @@ export default defineComponent({
-
+
diff --git a/packages/editor-ui/src/views/SettingsLogStreamingView.vue b/packages/editor-ui/src/views/SettingsLogStreamingView.vue
index e8abb67e64d2a..0f62a31aeed1c 100644
--- a/packages/editor-ui/src/views/SettingsLogStreamingView.vue
+++ b/packages/editor-ui/src/views/SettingsLogStreamingView.vue
@@ -175,7 +175,7 @@ export default defineComponent({
-
+
@@ -207,7 +207,7 @@ export default defineComponent({
@click:button="addDestination"
>
-
+
@@ -215,7 +215,7 @@ export default defineComponent({
-
+
@@ -225,7 +225,7 @@ export default defineComponent({
@click:button="goToUpgrade"
>
-
+
diff --git a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/AppsRequiringCredsNotice.vue b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/AppsRequiringCredsNotice.vue
index a640b49f9da50..ac5bfbe63a4eb 100644
--- a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/AppsRequiringCredsNotice.vue
+++ b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/AppsRequiringCredsNotice.vue
@@ -28,7 +28,7 @@ const appNodeCounts = computed(() => {
-
+
diff --git a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/SetupTemplateFormStep.vue b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/SetupTemplateFormStep.vue
index 2e64dece86b02..c482d3111477e 100644
--- a/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/SetupTemplateFormStep.vue
+++ b/packages/editor-ui/src/views/SetupWorkflowFromTemplateView/SetupTemplateFormStep.vue
@@ -95,7 +95,7 @@ const onCredentialModalOpened = () => {
:plural="credentials.usedBy.length"
scope="global"
>
-
+
diff --git a/packages/editor-ui/src/views/TemplatesSearchView.vue b/packages/editor-ui/src/views/TemplatesSearchView.vue
index 61ce8b42e0fe0..af0381d8209dd 100644
--- a/packages/editor-ui/src/views/TemplatesSearchView.vue
+++ b/packages/editor-ui/src/views/TemplatesSearchView.vue
@@ -410,7 +410,7 @@ export default defineComponent({
/>
-
+