DevExtreme React - Security Considerations

This help topic describes how to avoid potential security vulnerabilities when you use DevExtreme components.

See Also

HTML Encoding

HTML encoding is a simple technique that helps protect your web application from cross-site scripting (XSS) attacks. In an XSS attack, the attacker injects a malicious script into your web application. Every time a user visits the infected part of the application, this script runs. To prevent the code injection, user input must always be encoded (converted to plain text).

Encode User Input

Text editors, such as TextBox, Autocomplete, and HtmlEditor, do not encode user input. We recommend that you apply third-party sanitizing tools to the user input before you submit it to the server:

jQuery
index.js
index.html
$(function() {
    const editorInstance = $("#html-editor").dxHtmlEditor({
        // ...
    }).dxHtmlEditor("instance");

    $("#button").dxButton({
        useSubmitBehavior: true,
        text: "Submit the Form"
    });

    $("#form-container").on("submit", function(e) {
        const editorValue = editorInstance.option("value");
        // ...
        // Encode editorValue here with your favorite sanitizing tool before sending this value to the server 
        // ...

        e.preventDefault();
    });
});
<form action="your-action" id="form-container">
    <div id="html-editor"></div>
    <div id="button"></div>
</form>
Angular
app.component.html
app.component.ts
app.module.ts
<form
    action="your-action"
    (submit)="onFormSubmit($event)">
    <dx-html-editor ...
        [(value)]="editorValue">
    </dx-html-editor>

    <dx-button
        [useSubmitBehavior]="true"
        text="Submit the Form">
    </dx-button>
</form>
import { Component, ViewChild } from '@angular/core';
import { DxHtmlEditorComponent } from 'devextreme-angular';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {

    editorValue = '';

    onFormSubmit (e) {
        // ...
        // Encode this.editorValue here with your favorite sanitizing tool before sending this value to the server 
        // ...

        e.preventDefault();
    }
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

import { DxHtmlEditorModule, DxButtonModule } from 'devextreme-angular';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        DxHtmlEditorModule,
        DxButtonModule
    ],
    providers: [ ],
    bootstrap: [AppComponent]
})
export class AppModule { }
Vue
App.vue
<template>
    <form
        action="your-action"
        @submit="handleSubmit">
        <DxHtmlEditor ... 
            v-model:value="editorValue"
        />

        <DxButton
            :use-submit-behavior="true"
            text="Submit the Form"
        />
    </form>
</template>

<script>
import 'devextreme/dist/css/dx.light.css';

import { DxHtmlEditor } from 'devextreme-vue/html-editor';
import { DxButton } from 'devextreme-vue/button';

export default {
    components: {
        DxHtmlEditor, 
        DxButton
    },
    data: {
        editorValue: ''
    },
    methods: {
        handleSubmit(e) {
            // ...
            // Encode this.editorValue here with your favorite sanitizing tool before sending this value to the server 
            // ...

            e.preventDefault();
        }
    }
}
</script>
React
App.js
import React, { useCallback, useState } from 'react';
import 'devextreme/dist/css/dx.light.css';

import { HtmlEditor } from 'devextreme-react/html-editor';
import { Button } from 'devextreme-react/button';

const App = () => {
    const [editorValue, setEditorValue] = useState("");

    const onFormSubmit = useCallback((e) => {
        // ...
        // Encode editorValue here with your favorite sanitizing tool before sending this value to the server 
        // ...

        e.preventDefault();
    }, []);        

    const handleValueChange = (e) => {
        setEditorValue(e.value);
    };

    return (
        <form
            action="your-action"
            onSubmit={onFormSubmit}
            <HtmlEditor ... 
                value={editorValue}
                onValueChanged={handleValueChange}
            />

            <Button
                useSubmitBehavior={true}
                text="Submit the Form"
            />
        </form>
    );
}

export default App;

Encode Template Data

Angular, Vue, and React always encode values interpolated in templates. With other frameworks and libraries, use a third-party sanitizing tool as follows:

JavaScript
$(function() {
    $("#tabs").dxTabs({
        dataSource: tabs,
        width: 600,
        itemTemplate: function (itemData) {
            const encodedContent = // encode the itemData.content value; 
            return encodedContent;
        }
    });
});

const tabs = [{     
    id: 0,
    content: "<img src=1 onerror=alert('XSS') \/>" 
}, { 
    id: 1,
    content: "<script>alert('XSS')<\/script>" 
}, { 
    id: 2, 
    content: "Tab content" 
}];

When you insert unencoded content, it can open your application to XSS attacks:

DevExtreme Tabs with disabled HTML encoding

The encoded content is interpreted and displayed as text:

DevExtreme Tabs with enabled HTML encoding

Potentially Vulnerable API

Several components include API members that allow you to insert unencoded HTML. The following sections describe these potentially vulnerable API members.

customizeTooltip

The customizeTooltip callback function allows you to customize tooltips in SVG components. This function should return an object with individual tooltip settings. This object can include the html field that accepts an HTML string. If this string contains JavaScript code, the UI component executes it. This capability makes the component vulnerable to XSS attacks.

To guard against the attacks, ensure that the html value does not contain malicious code. Alternatively, you can use the text field. Unlike html values, text values are encoded. Refer to the following example to see the difference: customizeTooltip - Potential XSS Vulnerability.

encodeHtml

encodeHtml is a Boolean property that you can set for a column in the DataGrid and TreeList components. Its default value is true, which means that column values are encoded. If you set it to false, the encoding is disabled, and malicious code can be executed. We recommend that you keep this property set to true.

Open the following example to learn how disabling the encodeHtml property can affect your application: HTML Encoding in DataGrid. In this example, malicious code is saved in the data source:

JavaScript
const products = [{
    "ProductID": 1,
    "ProductName": "<img src=1 onerror=alert('XSS') \/>",
    // ...
}, {
    "ProductID": 2,
    "ProductName": "<script>alert('XSS')<\/script>",
    // ...
},
// ...
];

When encodeHtml is true, the DataGrid interprets this code as text and simply displays it:

DevExtreme DataGrid with enabled HTML encoding

If you set encodeHtml to false, the malicious code will be interpreted as script, and you will see an alert pop-up window:

DevExtreme DataGrid with disabled HTML encoding

html

Items in collection UI components (List, SelectBox, Toolbar, and similar components) can apply appearance based on data source fields (see the Default Templates article). html is one of such fields that specifies item markup. This field's values are not encoded, so ensure that they do not contain malicious code. Alternatively, you can use the text field. Unlike html values, text values are encoded.

The following example illustrates how the html field can lead to a potential vulnerability: HTML Encoding in List. In this example, both text and html values contain unsafe HTML, but html lines are commented out:

JavaScript
const products = [{
    "id": 1,
    "text": "<img src=1 onerror=alert('XSS') \/>",
    // "html": "<img src=1 onerror=alert('XSS') \/>"
}, {
    "ID": 2,
    "text": "<script>alert('XSS')<\/script>",
    // "html": "<script>alert('XSS')<\/script>"
}, {
    "id": 3,
    "text": "Product 1"
    // "html": "Product 1"
}];

When html is commented out, text applies. You can see that its values are interpreted as text and simply displayed:

DevExtreme List with enabled HTML encoding

Uncomment the html lines, and you will see an alert pop-up window. This is because unsafe HTML was interpreted as script and executed:

DevExtreme List with disabled HTML encoding

markers[].tooltip.text

The Map component can contain multiple markers. Each marker can display a tooltip. Its content is taken from the marker's tooltip.text property. This property can accept an HTML string that the component evaluates. Note that if the HTML contains JavaScript commands, they will be executed. Attackers can exploit this behavior for XSS. The following example illustrates such a case: Map.markers[].tooltip.text - Potential XSS Vulnerability

If markers data comes from an untrusted source, encode the tooltip.text value as follows:

JavaScript
const markers = [ ... ];
const encodeMessage = (message) => {
    // ...
    // Encode the `message` string with your favorite sanitizing tool
    // ...
    return encodedMessage;
};

const sanitizeMarkers = (markers) => {
    markers.forEach(marker => {
        marker.tooltip.text = encodeMessage(marker.tooltip.text);
    })
    return markers;
};

messageHtml

DevExtreme Dialog UI methods accept an unencoded HTML string as a dialog message. Encode this string in the following manner:

JavaScript
const message = "Are you sure?<script>alert('XSS')</script>";
// ...
// Encode the `message` string with your favorite sanitizing tool
// ...
DevExpress.ui.dialog.confirm(messageEncoded, "Confirm changes");

This code produces the following output:

DevExtreme Dialog: An Encoded String

noDataText

The noDataText property specifies text to display when the UI component does not contain any data. This property can accept an HTML string that the component evaluates. This evaluation, however,makes the noDataText property potentially vulnerable to XSS attacks. If the noDataText value comes from a third party (loaded from a data source, inputted by a user), encode this value to protect against harmful code.

The following example illustrates how an unsafe noDataText value can affect your application: noDataText - Potential XSS Vulnerability. You should encode the value as follows:

jQuery
index.js
const noDataTextUnsafe = "No data to display<img src=1 onerror=alert('XSS') \/>";
const encodeMessage = (message) => {
    // ...
    // Encode the `message` string with your favorite sanitizing tool
    // ...
    return encodedMessage;
};

$(function() {
    $("#simpleList").dxList({
        // ...
        noDataText: encodeMessage(noDataTextUnsafe)
    });
});
Angular
app.component.html
app.component.ts
app.module.ts
<dx-list ...
    [noDataText]="noDataTextEncoded">
</dx-list>
import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    noDataTextEncoded: string;
    constructor() {
        const noDataTextUnsafe = "No data to display<img src=1 onerror=alert('XSS') \/>";
        this.noDataTextEncoded = this.encodeMessage(noDataTextUnsafe);
    }
    encodeMessage (message) {
        // ...
        // Encode the `message` string with your favorite sanitizing tool
        // ...
        return encodedMessage;
    };

}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

import { DxListModule } from 'devextreme-angular';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        DxListModule
    ],
    providers: [ ],
    bootstrap: [AppComponent]
})
export class AppModule { }
Vue
App.vue
<template>
    <DxList ...
        :no-data-text="noDataTextEncoded"
    />
</template>

<script>
import 'devextreme/dist/css/dx.light.css';
import DxList from 'devextreme-vue/list';

const noDataTextUnsafe = "No data to display<img src=1 onerror=alert('XSS') \/>";

const encodeMessage = (message) => {
    // ...
    // Encode the `message` string with your favorite sanitizing tool
    // ...
    return encodedMessage;
};

export default {
    components: {
        DxList
    },
    data() {
        return {
            // ...
            noDataTextEncoded: encodeMessage(noDataTextUnsafe)
        }
    }
}
</script>
React
App.js
import React, { useState } from 'react';
import 'devextreme/dist/css/dx.light.css';

import List from 'devextreme-react/list';

const encodeMessage = (message) => {
    // ...
    // Encode the `message` string with your favorite sanitizing tool
    // ...
    return encodedMessage;
};

export default function App() {
    const noDataTextUnsafe = "No data to display<img src=1 onerror=alert('XSS') \/>";
    const noDataTextEncoded = encodeMessage(noDataTextUnsafe);

    return (
        <List ...
            noDataText={noDataTextEncoded}
        />
    );
}

Export Vulnerabilities

CSV Injection

If you export data from DataGrid or PivotGrid in CSV format, you should take the threat of a CSV Injection Attack (also known as a formula injection attack) into account. Such attacks involve an injection of a malicious character sequence that is interpreted as a formula and executed within a computer network. Cell values that start with the =, +, -, and @ characters can initiate an injection attack.

When executed, malicious code in a formula can tamper with user data, or allow unauthorized access to data and internal resources.

You can encode CSV files to prevent execution of potentially harmful code within them. Pass the encodeExecutableContent option as an argument of the configuration object of the exportDataGrid or exportPivotGrid function.

jQuery
JavaScript
HTML
$(function() {
    $("#dataGridContainer").dxDataGrid({
        // ...
        export: {
            enabled: true,
            formats: ['csv']
        },
        onExporting(e) {
            const workbook = new ExcelJS.Workbook();
            const worksheet = workbook.addWorksheet('Employees');

            DevExpress.excelExporter.exportDataGrid({
                component: e.component,
                worksheet: worksheet,
                encodeExecutableContent: true,
            }).then(() => {
                workbook.csv.writeBuffer().then((buffer) => {
                    saveAs(new Blob([buffer], { type: 'application/octet-stream' }), 'DataGrid.csv');
                });
            });
            e.cancel = true;
        }
    });
});
<head>
    <!-- ... -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.4.0/polyfill.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/3.3.1/exceljs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js"></script>
    <!-- reference the DevExtreme sources here -->
</head>
Angular
app.component.html
app.component.ts
app.module.ts
<dx-data-grid ...
    (onExporting)="onExporting($event)">
    <dxo-export
        [enabled]="true"
        [formats]="['csv']"
    ></dxo-export>
</dx-data-grid>
import { Component } from '@angular/core';
import { exportDataGrid } from 'devextreme/excel_exporter';
import { Workbook } from 'exceljs';
import saveAs from 'file-saver';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    onExporting(e) {
        const workbook = new Workbook();
        const worksheet = workbook.addWorksheet('Employees');

        exportDataGrid({
            component: e.component,
            worksheet: worksheet,
            encodeExecutableContent: true,
        }).then(() => {
            workbook.csv.writeBuffer().then((buffer) => {
                saveAs(new Blob([buffer], { type: 'application/octet-stream' }), 'DataGrid.csv');
            });
        });
        e.cancel = true;
    }
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';

import { DxDataGridModule } from 'devextreme-angular';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        DxDataGridModule
    ],
    providers: [ ],
    bootstrap: [AppComponent]
})
export class AppModule { }
Vue
App.vue
<template>
    <DxDataGrid ...
        @exporting="onExporting">
        <DxExport
            :enabled="true"
            :formats="['csv']"
        />
    </DxDataGrid>
</template>

<script>
// ...
import { DxDataGrid, DxExport } from 'devextreme-vue/data-grid';
import { exportDataGrid } from 'devextreme/excel_exporter';
import { Workbook } from 'exceljs';
import saveAs from 'file-saver';

export default {
    components: {
        DxDataGrid,
        DxExport
    },
    methods: {
        onExporting(e) {
            const workbook = new Workbook();
            const worksheet = workbook.addWorksheet('Employees');

            exportDataGrid({
                component: e.component,
                worksheet: worksheet,
                encodeExecutableContent: true,
            }).then(function() {
                workbook.csv.writeBuffer().then((buffer) => {
                    saveAs(new Blob([buffer], { type: 'application/octet-stream' }), 'DataGrid.csv');
                });
            });
            e.cancel = true;
        }
    }
}
</script>
React
App.js
import React, { useCallback } from 'react';
// ...

import DataGrid, { Export } from 'devextreme-react/data-grid';
import { Workbook } from 'exceljs';
import saveAs from 'file-saver';
import { exportDataGrid } from 'devextreme/excel_exporter';

const App = () => {
    const onExporting = useCallback((e) => {
        const workbook = new Workbook();
        const worksheet = workbook.addWorksheet('Employees');

        exportDataGrid({
            component: e.component,
            worksheet: worksheet,
            encodeExecutableContent: true,
        }).then(function() {
            workbook.csv.writeBuffer().then((buffer) => {
                saveAs(new Blob([buffer], { type: 'application/octet-stream' }), 'DataGrid.csv');
            });
        });
        e.cancel = true;
    }, []);

    return (
        <DataGrid ...
            onExporting={onExporting}>
            <Export 
                enabled={true}
                formats={['csv']}
            />
        </DataGrid>
    );
}

export default App;