vue.jsvuejs2xssbuefyhtml-sanitizing

How to use Buefy's Dialog component with user provided content and be safe in terms of XSS


Buefy's Dialog component expects a message prop - string. According to a documentation, that string can contain HTML. I would like to use template values in the string, but of course is should be XSS safe.

Current unsafe example

This is unsafe, as this.name is unsafe. I could use a NPM package to html encode the name, but I really prefer to use Vue.

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  props: {
    name: { type: String, required: false },
  },
  methods: {
    showModal() {
      this.$buefy.dialog.confirm({
        title: 'myTitle',
        message: `<p>Hello ${this.name}</p>`, // unsafe - possible XSS!
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });
    },
  },
});
</script>

This is an issue of the used Buefy component, as documented here:

enter image description here

Desired Setup

I've created a new component, in this example I call it ModalMessage.Vue

<template>
    <p>Hello {{name}}</p>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  props: {
    name: { type: String, required: true },
  },
});
</script>

Then I like to render the ModalMessage.Vue to a string in Typescript:

<script lang="ts">
import Vue from 'vue';
import ModalMessage from 'ModalMessage.vue';

export default Vue.extend({
  props: {
    name: { type: String, required: false },
  },
  methods: {
    showModal() {
      this.$buefy.dialog.confirm({
        title: 'myTitle',
        message:, // todo render ModalMessage and pass name prop
        cancelText: 'Cancel',
        confirmText: 'OK',
        type: 'is-success',
        onConfirm: async () => {
          // something
        },
      });
    },
  },
});
</script>

Question

How could I render the ModalMessage.Vue, and passing the name prop, to a string?

I'm pretty sure this is possible - I have seen it in the past. Unfortunately I cannot find it on the web or StackOverflow. I could only find questions with rendering a template from string, but I don't need that - it needs to be to string.


Solution

  • Imho your real question is "How to use Buefy's Dialog component with user provided content and be safe in terms of XSS"

    So what you want is to create some HTML, include some user provided content (this.name) within that HTML content and display it in a Dialog. You are right that putting unfiltered user input into a message parameter of Dialog is not safe (as clearly noted in Buefy docs)

    But your "Desired Setup" seems unnecessary complicated. Imho the easiest way is to use (poorly documented) fact that message parameter of Buefy Dialog configuration objects can be an Array of VNode's instead of a string. It is poorly documented but it is very clear from the source here and here that if you pass an array of VNode's, Buefy puts that content into Dialogs default slot instead of rendering it using v-html (which is the dangerous part)

    And easiest way to get Array of VNode in Vue is to use slots...

    So the component:

    <script lang="ts">
    import Vue from 'vue';
    
    export default Vue.extend({
      methods: {
        showModal() {
          this.$buefy.dialog.confirm({
            title: 'myTitle',
            message: this.$slots.default, // <- this is important part
            cancelText: 'Cancel',
            confirmText: 'OK',
            type: 'is-success',
            onConfirm: async () => {
              // something
            },
          });
        },
      },
    });
    </script>
    

    and it's usage:

    <MyDialog>
      <p>Hello {{name}}</p>
    </MyDialog>
    

    or

    <MyDialog>
      <ModalMessage :name="name" />
    </MyDialog>
    

    In both cases if the name contains any HTML, it will be encoded by Vue

    Here is a simple demo of the technique described above (using plain JS - not TS)