DevExtreme Angular - Security Considerations

This help topic describes potential security vulnerabilities and what you can do as an application developer to mitigate risks when using DevExtreme UI components.

Angular
See Also
Vue
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 threat actor injects a malicious script into your web application. Every time a user visits the impacted portion of the application, this script executes. To prevent code injection, user input must always be encoded (converted to plain text).

Encode User Input

DevExtreme text editors, such as TextBox, Autocomplete, and HtmlEditor, do not encode user input. We recommend that you apply third-party sanitizing tools to user input before submitting 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;

Potentially Vulnerable API

Several DevExtreme UI components include API members that allow you to insert unencoded HTML. The following sections describe these members and actions you should take to mitigate security-related risks.

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 an html field that accepts an HTML string. If this string contains JavaScript code, the UI component will execute it.

To guard against XSS attacks when using the customizeTooltip callback function, ensure that the html value does not contain malicious code or use the text field instead. Unlike html values, text values are encoded. Refer to the following example to see the difference:

View in CodePen

encodeHtml

encodeHtml is a Boolean property that you can set for a column in the DataGrid, a column in the TreeList, a column in the Gantt, a cell in the PivotGrid, and a header filter value in the PivotGridFieldChooser. The default encodeHtml value is true, and the component encodes corresponding values. If you set it to false, the component disables encoding, and malicious code can be executed. To mitigate security-related risks, we recommend that you always set this property to true.

To see what can occur if you disable the encodeHtml property open the following example:

View in CodePen

In this example, the data source stores the malicious code:

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 DevExtreme collection UI components (List, SelectBox, Toolbar, and similar components) can apply appearance based on data source fields (see our Default Templates article for more information). html is one of the fields that specifies item markup. Values for this field are not encoded. As such, you must ensure that these values do not contain malicious code. Alternatively, you can use the text field. Unlike html values, text values are encoded.

The following example illustrates how improper use of the html field can create a vulnerability:

View in CodePen

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 a 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 – a string which is evaluated by the component. If the HTML contains JavaScript commands, it will be executed. The following example illustrates what can occur if appropriate measures are not taken: Map.markers[].tooltip.text - Potential XSS Vulnerability

If markers data is obtained from an untrusted source, encode your 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 – a string the component will evaluate. During evaluation, the noDataText property can make your app vulnerable to XSS attacks. If the noDataText value is obtained from a third party (loaded from a data source, entered by a user), encode this value to protect against threat actors/harmful code.

The following example illustrates how an unsafe noDataText value can affect your application:

View in CodePen

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 the DevExtreme DataGrid or PivotGrid in CSV format, your app may be vulnerable to a CSV Injection Attack (also known as a formula injection attack) unless appropriate measures are taken. CSV Injection Attacks involve the injection of a malicious character sequence that is interpreted as a formula and executed within a computer network. Cell values that start with =, +, -, and @ characters can initiate an injection attack.

When executed, malicious code in a formula can alter user data, or allow unauthorized access to data or 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');
                });
            });
        }
    });
});
<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');
            });
        });
    }
}
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');
                });
            });
        }
    }
}
</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');
            });
        });
    }, []);

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

export default App;

ExcelJS CSP Threats

The DevExtreme DataGrid and PivotGrid components use the ExcelJS third-party library to export data to Excel. If you implement export functionality and apply CSP rules, you should include the following initialization code before code that loads ExcelJS sources: window.regeneratorRuntime = null;

jQuery
HTML
JavaScript
<head>
    <!-- ... -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-uqcb8z'
    cdnjs.cloudflare.com;" />
    <script nonce="uqcb8z">
        window.regeneratorRuntime = null;
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/3.3.1/exceljs.min.js"></script>
    <!-- reference the DevExtreme sources here -->
</head>
$(function(){
    const dataGrid = $('#gridContainer').dxDataGrid({
        // ...
        export: {
            enabled: true,
            formats: ['xlsx'],
        },
        onExporting(e) {
            if (e.format === 'xlsx') {
                // ...
            } 
        },
    }).dxDataGrid('instance');
});
Angular
index.html
app.component.html
app.component.ts
<head>
    <!-- ... -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-uqcb8z' cdnjs.cloudflare.com;" />
    <script nonce="uqcb8z">
        window.regeneratorRuntime = null;
    </script>
</head>

<!-- ... -->
<dx-data-grid ... 
    (onExporting)="onExporting($event)"
>
    <!-- ... -->
    <dxo-export
        [enabled]="true"
        [formats]="['xlsx']"
    ></dxo-export>
</dx-data-grid>
import { Component } from '@angular/core';
import { Workbook } from 'exceljs';
import { exportDataGrid } from 'devextreme/excel_exporter';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    onExporting(e) {
        if (e.format === 'xlsx') {
            // ...
        } 
    }
}
Vue
index.html
App.vue (Options API)
App.vue (Composition API)
<head>
    <!-- ... -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-uqcb8z' cdnjs.cloudflare.com;" />
    <script nonce="uqcb8z">
        window.regeneratorRuntime = null;
    </script>
</head>

<!-- ... -->
<template>
    <div>
        <DxDataGrid ...
            @exporting="onExporting"
        >
            <!-- ... -->
            <DxExport
                :enabled="true"
                :formats="['xlsx']"
            />
        </DxDataGrid>
    </div>
</template>

<script>
import 'devextreme/dist/css/dx.light.css';
import { DxDataGrid, DxExport } from 'devextreme-vue/data-grid';
import { Workbook } from 'exceljs';

export default {
    components: {
        DxDataGrid
    }
    data() {
        return {
            // ...
        };
    },
    methods: {
        onExporting(e) {
            if (e.format === 'xlsx') {
                // ...
            } 
        },
    },
}
</script>
<template>
    <div>
        <DxDataGrid ...
            @exporting="onExporting"
        >
            <!-- ... -->
            <DxExport
                :enabled="true"
                :formats="['xlsx']"
            />
        </DxDataGrid>
    </div>
</template>

<script setup>
import 'devextreme/dist/css/dx.light.css';
import { DxDataGrid, DxExport } from 'devextreme-vue/data-grid';
import { Workbook } from 'exceljs';

const onExporting = (e) => {
    if (e.format === 'xlsx') {
        // ...
    } 
}
</script>
React
index.html
App.js
<head>
    <!-- ... -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-uqcb8z' cdnjs.cloudflare.com;" />
    <script nonce="uqcb8z">
        window.regeneratorRuntime = null;
    </script>
</head>

<!-- ... -->
import React from 'react';
import 'devextreme/dist/css/dx.light.css';
import DataGrid, { Export } from 'devextreme-react/data-grid';
import { Workbook } from 'exceljs';

const exportFormats = ['xlsx'];

export default function App() {
    const onExporting = React.useCallback((e) => {
        if (e.format === 'xlsx') {
            // ...
        } 
    });
    return (
        <React.Fragment>
            <div>
                <DataGrid ...
                    onExporting={onExporting}
                >
                    {/* ... */}
                    <Export enabled={true} formats={exportFormats}>
                </DataGrid>
            </div>
        </React.Fragment>
    );
}

Content Security Policy

Content Security Policy (CSP) is a security feature that helps detect and mitigate certain types of attacks, such as clickjacking, cross-site scripting (XSS), and other malicious code injection attacks. The impact of such attacks varies but can include data theft, page spoofing, malware distribution, site defacement, etc. It is important to ensure that DevExtreme UI components are compatible with CSP.

If you want to apply CSP rules, define a <meta> tag and configure your policy. For DevExtreme UI components, you can specify the following set of directives:

HTML
<meta
    http-equiv="Content-Security-Policy"
    content="default-src 'self'; img-src https://* data:; child-src 'none';" 
/> 
NOTE
Do not use unsafe CSP directives to avoid any unforeseen threats.

Common CSP Directives

You can specify a set of CSP rules to define resources that your site allows or restricts. The following table lists common CSP directives and source values:

Directive Example Description
default-src default-src 'self' cdn.example.com; Default policy. Allow everything but only from the same origin.
script-src script-src 'self' js.example.com; Only allow scripts from the same origin.
style-src style-src 'self' css.example.com; Defines authorized sources for stylesheets (CSS).
object-src object-src 'self'; Defines authorized sources for plugins (for example, <object>, <embed> or <applet>).
img-src img-src 'self' img.example.com; Defines authorized sources for images, or link element related to an image type (for example, rel="icon").
frame-src frame-src 'self'; Defines authorized sources for loading frames (iframe or frame).
form-action form-action 'self'; Defines valid sources that can be used as an HTML <form> action.

CSP Source List

All directives ending with -src supply corresponding values referred to as a source list. You can separate multiple source list values with a space, except for the none value, which should be used on its own.

Source Value Example Description
* img-src * Wildcard, allows any URL except "data:", "blob:", and "filesystem:" schemes.
'none' object-src 'none' Rejects resources from any source.
'self' script-src 'self' Allows a web site to load resources from the same origin (same scheme, host and port).
data: img-src 'self' data: Allows a web site to load resources via the data scheme (for example, Base64 encoded images).
domain.example.com img-src domain. example.com Allows a web site to load resources from the specified domain name.
*.example.com img-src *.example.com Allows a web site to load resources from any subdomain under example.com.
https://cdn.com img-src https://cdn.com Allows a web site to load resources only from the specified domain, and only over HTTPS.
https: img-src https: Allows a web site to load resources from any domain, but only over HTTPS.
'unsafe-inline' script-src 'unsafe-inline' Allows a web site to use inline source elements such as style attribute, onclick, or script tag bodies (depends on the context of the source it is applied to) and javascript: URIs.
'unsafe-eval' script-src 'unsafe-eval' Allows a web site to perform unsafe dynamic code evaluation such as JavaScript eval().
'nonce-' script-src 'nonce-rAnd0m' Allows an inline script or CSS to execute if the script (for example, <script nonce="rAnd0m">) tag contains a nonce attribute matching the nonce specifed in the CSP header. The nonce should be a secure random string, and should not be reused. CSP Level 2
'strict-dynamic' script-src 'strict-dynamic' Enables an allowed script to load additional scripts via non-"parser-inserted" script elements (for example, document.createElement('script'); is allowed). CSP Level 3
'unsafe-hashes' script-src 'unsafe-hashes' Allows you to enable scripts in event handlers (for example, onclick). Does not apply to javascript: or inline <script>. CSP Level 3

CSP Directives for Map Integrations

If you want to integrate DevExtreme UI components with Google and Bing maps API, specify the following set of CSP directives:

HTML
// Bing maps
<meta  
    http-equiv="Content-Security-Policy"
    content="default-src 'self';  
        script-src 'self' https://*.bing.com https://*.virtualearth.net https://ssl.gstatic.com;  
        style-src 'self' 'unsafe-inline' https://*.bing.com https://*.virtualearth.net;  
        img-src 'self' data: https://*.bing.com https://*.virtualearth.net;  
        font-src 'self' data:;  
        connect-src 'self' https://*.bing.com" 
/> 
// Google maps
<meta 
    http-equiv="Content-Security-Policy" 
    content="default-src 'self' https://*.googleapis.com/ https://*.gstatic.com;  
        script-src 'unsafe-inline' 'unsafe-eval' 'self' https://*.googleapis.com/ https://*.gstatic.com;  
        style-src 'unsafe-inline' 'self' https://*.googleapis.com/ https://*.gstatic.com;  
        img-src 'self' data: https://*.googleapis.com/ https://*.gstatic.com;" 
/>

CSP Directives for Themes and Icons

Material and Fluent (all trademarks or registered trademarks are property of their respective owners) themes do not support the default-src 'self' directive out of the box. To enable this directive, export such themes from DevExtreme ThemeBuilder and remove links to external fonts.

You can also use ThemeBuilder CLI to export the theme:

// Fluent theme
npx devextreme-cli build-theme --base-theme="fluent.blue.light" --remove-external-resources

// Material theme
npx devextreme-cli build-theme --base-theme="material.blue.light" --remove-external-resources
NOTE
The img-src data: attribute is required to display custom SVG-based icons in certain components.