CVE-2022-23552: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Grafana is an open-source platform for monitoring and observability. Starting with the 8.1 branch and prior to versions 8.5.16, 9.2.10, and 9.3.4, Grafana had a stored XSS vulnerability affecting the core plugin GeoMap. The stored XSS vulnerability was possible because SVG files weren't properly sanitized and allowed arbitrary JavaScript to be executed in the context of the currently authorized user of the Grafana instance. 

An attacker needs to have the Editor role in order to change a panel to include either an external URL to a SVG-file containing JavaScript, or use the `data:` scheme to load an inline SVG-file containing JavaScript. This means that vertical privilege escalation is possible, where a user with Editor role can change to a known password for a user having Admin role if the user with Admin role executes malicious JavaScript viewing a dashboard.   

Users may upgrade to version 8.5.16, 9.2.10, or 9.3.4 to receive a fix.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2023-01-27T23:15Z
Repository: https://github.com/grafana/grafana
Language: Other
Files changed: package.json, packages/grafana-ui/src/components/Icon/Icon.tsx, public/app/core/components/SVG/SanitizedSVG.tsx, public/app/features/canvas/elements/icon.tsx, public/app/features/dimensions/editors/FileUploader.tsx, public/app/features/dimensions/editors/ResourceCards.tsx, public/app/features/dimensions/editors/ResourcePicker.tsx, public/app/features/dimensions/editors/URLPickerTab.tsx, public/app/features/query-library/components/QueryEditorDrawerHeader.tsx, public/app/features/storage/FileView.tsx, public/app/plugins/panel/geomap/components/MarkersLegend.tsx, public/app/plugins/panel/geomap/style/markers.ts, yarn.lock
Commit date: 2023-01-25 10:37:29 -0800
Commit message: SVG: Add dompurify preprocessor step (#62143)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
Diff: diff --git a/package.json b/package.json
index a9079fa8025..c9255b114bd 100644
--- a/package.json
+++ b/package.json
@@ -121,10 +121,11 @@
     "@types/common-tags": "^1.8.0",
     "@types/d3": "7.4.0",
     "@types/d3-force": "^2.1.0",
     "@types/d3-scale-chromatic": "1.3.1",
     "@types/debounce-promise": "3.1.5",
+    "@types/dompurify": "^2",
     "@types/eslint": "8.4.9",
     "@types/file-saver": "2.0.5",
     "@types/glob": "^8.0.0",
     "@types/google.analytics": "^0.0.42",
     "@types/gtag.js": "^0.0.12",
@@ -314,10 +315,11 @@
     "d3-force": "^2.1.1",
     "d3-scale-chromatic": "1.5.0",
     "dangerously-set-html-content": "1.0.9",
     "date-fns": "2.29.3",
     "debounce-promise": "3.1.2",
+    "dompurify": "^2.4.1",
     "emotion": "11.0.0",
     "eventemitter3": "4.0.7",
     "fast-deep-equal": "^3.1.3",
     "fast-json-patch": "3.1.1",
     "file-saver": "2.0.5",
diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx
index c601c17adc1..a411c06a6a1 100644
--- a/packages/grafana-ui/src/components/Icon/Icon.tsx
+++ b/packages/grafana-ui/src/components/Icon/Icon.tsx
@@ -55,13 +55,17 @@ export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
 
     if (!isIconName(name)) {
       console.warn('Icon component passed an invalid icon name', name);
     }
 
+    if (!name || name.includes('..')) {
+      return <div ref={ref}>invalid icon name</div>;
+    }
+
     const svgSize = getSvgSize(size);
     const svgHgt = svgSize;
-    const svgWid = name?.startsWith('gf-bar-align') ? 16 : name?.startsWith('gf-interp') ? 30 : svgSize;
+    const svgWid = name.startsWith('gf-bar-align') ? 16 : name.startsWith('gf-interp') ? 30 : svgSize;
     const subDir = getIconSubDir(name, type);
     const svgPath = `${iconRoot}${subDir}/${name}.svg`;
 
     return (
       <div className={styles.container} {...divElementProps} ref={ref}>
diff --git a/public/app/core/components/SVG/SanitizedSVG.tsx b/public/app/core/components/SVG/SanitizedSVG.tsx
new file mode 100644
index 00000000000..9a2628cd63d
--- /dev/null
+++ b/public/app/core/components/SVG/SanitizedSVG.tsx
@@ -0,0 +1,18 @@
+import * as DOMPurify from 'dompurify';
+import React from 'react';
+import SVG, { Props } from 'react-inlinesvg';
+
+export const SanitizedSVG = (props: Props) => {
+  return <SVG {...props} cacheRequests={true} preProcessor={getCleanSVG} />;
+};
+
+let cache = new Map<string, string>();
+
+function getCleanSVG(code: string): string {
+  let clean = cache.get(code);
+  if (!clean) {
+    clean = DOMPurify.sanitize(code, { USE_PROFILES: { svg: true, svgFilters: true } });
+    cache.set(code, clean);
+  }
+  return clean;
+}
diff --git a/public/app/features/canvas/elements/icon.tsx b/public/app/features/canvas/elements/icon.tsx
index cb4920e8948..8d7c8a4c876 100644
--- a/public/app/features/canvas/elements/icon.tsx
+++ b/public/app/features/canvas/elements/icon.tsx
@@ -1,10 +1,10 @@
 import { css } from '@emotion/css';
 import { isString } from 'lodash';
 import React, { CSSProperties } from 'react';
-import SVG from 'react-inlinesvg';
 
+import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
 import {
   ColorDimensionConfig,
   ResourceDimensionConfig,
   ResourceDimensionMode,
   getPublicOrAbsoluteUrl,
@@ -57,11 +57,11 @@ export function IconDisplay(props: CanvasElementProps) {
     height: '100%',
     width: '100%',
   };
 
   return (
-    <SVG
+    <SanitizedSVG
       onClick={onClick}
       src={data.path}
       style={svgStyle}
       className={svgStyle.strokeWidth ? svgStrokePathClass : undefined}
     />
diff --git a/public/app/features/dimensions/editors/FileUploader.tsx b/public/app/features/dimensions/editors/FileUploader.tsx
index 9a8cf21712d..7e58eaf4617 100644
--- a/public/app/features/dimensions/editors/FileUploader.tsx
+++ b/public/app/features/dimensions/editors/FileUploader.tsx
@@ -1,13 +1,14 @@
 import { css } from '@emotion/css';
 import React, { Dispatch, SetStateAction, useState } from 'react';
-import SVG from 'react-inlinesvg';
 
 import { GrafanaTheme2 } from '@grafana/data';
 import { FileDropzone, useStyles2, Button, DropzoneFile, Field } from '@grafana/ui';
+import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
 
 import { MediaType } from '../types';
+
 interface Props {
   setFormData: Dispatch<SetStateAction<FormData>>;
   mediaType: MediaType;
   setUpload: Dispatch<SetStateAction<boolean>>;
   newValue: string;
@@ -34,11 +35,11 @@ export const FileUploader = ({ mediaType, setFormData, setUpload, error }: Props
   const [file, setFile] = useState<string>('');
 
   const Preview = () => (
     <Field label="Preview">
       <div className={styles.iconPreview}>
-        {mediaType === MediaType.Icon && <SVG src={file} className={styles.img} />}
+        {mediaType === MediaType.Icon && <SanitizedSVG src={file} className={styles.img} />}
         {mediaType === MediaType.Image && <img src={file} alt="Preview of the uploaded file" className={styles.img} />}
       </div>
     </Field>
   );
 
diff --git a/public/app/features/dimensions/editors/ResourceCards.tsx b/public/app/features/dimensions/editors/ResourceCards.tsx
index 49aa6ea804c..57d01604cda 100644
--- a/public/app/features/dimensions/editors/ResourceCards.tsx
+++ b/public/app/features/dimensions/editors/ResourceCards.tsx
@@ -1,13 +1,13 @@
 import { css, cx } from '@emotion/css';
 import React, { memo, CSSProperties } from 'react';
-import SVG from 'react-inlinesvg';
 import AutoSizer from 'react-virtualized-auto-sizer';
 import { areEqual, FixedSizeGrid as Grid } from 'react-window';
 
 import { GrafanaTheme2 } from '@grafana/data';
 import { useTheme2, stylesFactory } from '@grafana/ui';
+import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
 
 import { ResourceItem } from './FolderPickerTab';
 
 interface CellProps {
   columnIndex: number;
@@ -36,11 +36,11 @@ function Cell(props: CellProps) {
           key={card.value}
           className={selected === card.value ? cx(styles.card, styles.selected) : styles.card}
           onClick={() => onChange(card.value)}
         >
           {card.imgUrl.endsWith('.svg') ? (
-            <SVG src={card.imgUrl} className={styles.img} />
+            <SanitizedSVG src={card.imgUrl} className={styles.img} />
           ) : (
             <img src={card.imgUrl} alt="" className={styles.img} />
           )}
           <h6 className={styles.text}>{card.label.slice(0, -4)}</h6>
         </div>
diff --git a/public/app/features/dimensions/editors/ResourcePicker.tsx b/public/app/features/dimensions/editors/ResourcePicker.tsx
index 30246d6aa0d..ecfb63f3ef4 100644
--- a/public/app/features/dimensions/editors/ResourcePicker.tsx
+++ b/public/app/features/dimensions/editors/ResourcePicker.tsx
@@ -1,8 +1,7 @@
 import { css } from '@emotion/css';
 import React, { createRef } from 'react';
-import SVG from 'react-inlinesvg';
 
 import { GrafanaTheme2 } from '@grafana/data';
 import {
   Button,
   InlineField,
@@ -13,10 +12,11 @@ import {
   PopoverController,
   useStyles2,
   useTheme2,
 } from '@grafana/ui';
 import { closePopover } from '@grafana/ui/src/utils/closePopover';
+import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
 
 import { getPublicOrAbsoluteUrl } from '../resource';
 import { MediaType, ResourceFolderName, ResourcePickerSize } from '../types';
 
 import { ResourcePickerPopover } from './ResourcePickerPopover';
@@ -54,11 +54,11 @@ export const ResourcePicker = (props: Props) => {
     fill: theme.visualization.getColorByName(color),
   };
 
   const renderSmallResourcePicker = () => {
     if (value && sanitizedSrc) {
-      return <SVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />;
+      return <SanitizedSVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />;
     } else {
       return (
         <LinkButton variant="primary" fill="text" size="sm">
           Set icon
         </LinkButton>
@@ -71,11 +71,11 @@ export const ResourcePicker = (props: Props) => {
       <InlineField label={null} grow>
         <Input
           value={name}
           placeholder={placeholder}
           readOnly={true}
-          prefix={sanitizedSrc && <SVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />}
+          prefix={sanitizedSrc && <SanitizedSVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />}
           suffix={<Button icon="times" variant="secondary" fill="text" size="sm" onClick={onClear} />}
         />
       </InlineField>
     </InlineFieldRow>
   );
diff --git a/public/app/features/dimensions/editors/URLPickerTab.tsx b/public/app/features/dimensions/editors/URLPickerTab.tsx
index 6cf7ea8df8b..51504e36dd2 100644
--- a/public/app/features/dimensions/editors/URLPickerTab.tsx
+++ b/public/app/features/dimensions/editors/URLPickerTab.tsx
@@ -1,11 +1,11 @@
 import { css } from '@emotion/css';
 import React, { Dispatch, SetStateAction } from 'react';
-import SVG from 'react-inlinesvg';
 
 import { GrafanaTheme2 } from '@grafana/data';
 import { Field, Input, Label, useStyles2 } from '@grafana/ui';
+import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
 
 import { getPublicOrAbsoluteUrl } from '../resource';
 import { MediaType } from '../types';
 
 interface Props {
@@ -31,11 +31,11 @@ export const URLPickerTab = (props: Props) => {
         <Input onChange={(e) => setNewValue(e.currentTarget.value)} value={newValue} />
       </Field>
       <div className={styles.iconContainer}>
         <Field label="Preview">
           <div className={styles.iconPreview}>
-            {mediaType === MediaType.Icon && <SVG src={imgSrc} className={styles.img} />}
+            {mediaType === MediaType.Icon && <SanitizedSVG src={imgSrc} className={styles.img} />}
             {mediaType === MediaType.Image && newValue && (
               <img src={imgSrc} alt="Preview of the selected URL" className={styles.img} />
             )}
           </div>
         </Field>
diff --git a/public/app/features/query-library/components/QueryEditorDrawerHeader.tsx b/public/app/features/query-library/components/QueryEditorDrawerHeader.tsx
index 2f3fe1d4a68..fea76c8c8e6 100644
--- a/public/app/features/query-library/components/QueryEditorDrawerHeader.tsx
+++ b/public/app/features/query-library/components/QueryEditorDrawerHeader.tsx
@@ -1,11 +1,11 @@
 import { css, cx } from '@emotion/css';
 import React, { useEffect, useRef, useState } from 'react';
-import InlineSVG from 'react-inlinesvg/esm';
 
 import { GrafanaTheme2 } from '@grafana/data/src';
 import { Button, HorizontalGroup, Icon, IconName, useStyles2 } from '@grafana/ui';
+import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
 
 import { useAppNotification } from '../../../core/copy/appNotification';
 import { SavedQuery } from '../api/SavedQueriesApi';
 import { getSavedQuerySrv } from '../api/SavedQueriesSrv';
 import { implementationComingSoonAlert } from '../utils';
@@ -148,11 +148,11 @@ export const QueryEditorDrawerHeader = ({ savedQuery, onDismiss, onSavedQueryCha
                     <li key={key}>
                       {/*eslint-disable-next-line jsx-a11y/anchor-is-valid*/}
                       <a onClick={implementationComingSoonAlert}>
                         <div>
                           {option.src ? (
-                            <InlineSVG src={option.src} className={styles.optionSvg} />
+                            <SanitizedSVG src={option.src} className={styles.optionSvg} />
                           ) : (
                             <Icon name={option.icon} className={styles.menuIconClassName} />
                           )}
                         </div>
                         <span className="dropdown-item-text">{option.label}</span>
diff --git a/public/app/features/storage/FileView.tsx b/public/app/features/storage/FileView.tsx
index 0405ffe2433..efdf0324e5f 100644
--- a/public/app/features/storage/FileView.tsx
+++ b/public/app/features/storage/FileView.tsx
@@ -1,14 +1,14 @@
 import { css } from '@emotion/css';
 import { isString } from 'lodash';
 import React, { useMemo } from 'react';
-import SVG from 'react-inlinesvg';
 import { useAsync } from 'react-use';
 import AutoSizer from 'react-virtualized-auto-sizer';
 
 import { DataFrame, GrafanaTheme2 } from '@grafana/data';
 import { CodeEditor, useStyles2 } from '@grafana/ui';
+import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
 
 import { getGrafanaStorage } from './storage';
 import { StorageView } from './types';
 
 interface FileDisplayInfo {
@@ -53,11 +53,11 @@ export function FileView({ listing, path, onPathChange, view }: Props) {
 
   switch (info.category) {
     case 'svg':
       return (
         <div>
-          <SVG src={src} className={styles.icon} />
+          <SanitizedSVG src={src} className={styles.icon} />
         </div>
       );
     case 'image':
       return (
         <div>
diff --git a/public/app/plugins/panel/geomap/components/MarkersLegend.tsx b/public/app/plugins/panel/geomap/components/MarkersLegend.tsx
index 523c6c6ae3b..2833351acf2 100644
--- a/public/app/plugins/panel/geomap/components/MarkersLegend.tsx
+++ b/public/app/plugins/panel/geomap/components/MarkersLegend.tsx
@@ -1,16 +1,16 @@
 import { css, cx } from '@emotion/css';
 import BaseLayer from 'ol/layer/Base';
 import React, { useMemo } from 'react';
-import SVG from 'react-inlinesvg';
 import { useObservable } from 'react-use';
 import { of } from 'rxjs';
 
 import { DataFrame, formattedValueToString, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data';
 import { getMinMaxAndDelta } from '@grafana/data/src/field/scale';
 import { useStyles2, VizLegendItem } from '@grafana/ui';
 import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
+import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
 import { config } from 'app/core/config';
 import { DimensionSupplier } from 'app/features/dimensions';
 import { getThresholdItems } from 'app/plugins/panel/state-timeline/utils';
 
 import { StyleConfigState } from '../style/types';
@@ -56,11 +56,11 @@ export function MarkersLegend(props: MarkersLegendProps) {
   if (color && symbol && !colorField) {
     return (
       <div className={style.infoWrap}>
         <div className={style.layerName}>{layerName}</div>
         <div className={cx(style.layerBody, style.fixedColorContainer)}>
-          <SVG
+          <SanitizedSVG
             src={`public/${symbol}`}
             className={style.legendSymbol}
             title={'Symbol'}
             style={{ fill: color, opacity: opacity }}
           />
diff --git a/public/app/plugins/panel/geomap/style/markers.ts b/public/app/plugins/panel/geomap/style/markers.ts
index 9dd330565e7..cc3ab6a4898 100644
--- a/public/app/plugins/panel/geomap/style/markers.ts
+++ b/public/app/plugins/panel/geomap/style/markers.ts
@@ -1,5 +1,6 @@
+import * as DOMPurify from 'dompurify';
 import { Fill, RegularShape, Stroke, Circle, Style, Icon, Text } from 'ol/style';
 import tinycolor from 'tinycolor2';
 
 import { Registry, RegistryItem } from '@grafana/data';
 import { config } from '@grafana/runtime';
@@ -245,10 +246,12 @@ async function prepareSVG(url: string, size?: number): Promise<string> {
   return fetch(url, { method: 'GET' })
     .then((res) => {
       return res.text();
     })
     .then((text) => {
+      text = DOMPurify.sanitize(text, { USE_PROFILES: { svg: true, svgFilters: true } });
+
       const parser = new DOMParser();
       const doc = parser.parseFromString(text, 'image/svg+xml');
       const svg = doc.getElementsByTagName('svg')[0];
       if (!svg) {
         return '';
diff --git a/yarn.lock b/yarn.lock
index 4aac1aa2a42..65143dc9c5e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10807,10 +10807,19 @@ __metadata:
   resolution: "@types/deep-freeze@npm:0.1.2"
   checksum: 16a9f73ad2753049ae3d3b6d2b2d972bb47f21e237fd4a3d2a7b36bac9f69bd2b8671209d5d47d493e92861e283b1e999ec81260b158dd08d1855f8086ac8d40
   languageName: node
   linkType: hard
 
+"@types/dompurify@npm:^2":
+  version: 2.4.0
+  resolution: "@types/dompurify@npm:2.4.0"
+  dependencies:
+    "@types/trusted-types": "*"
+  checksum: b48cd81e997794ebc390c7c5bef1a67ec14a6f2f0521973e07e06af186c7583abe114d94d24868c0632b9573f5bd77131a4b76f3fffdf089ba99a4e53dd46c39
+  languageName: node
+  linkType: hard
+
 "@types/eslint-scope@npm:^3.7.3":
   version: 3.7.4
   resolution: "@types/eslint-scope@npm:3.7.4"
   dependencies:
     "@types/eslint": "*"
@@ -11988,10 +11997,17 @@ __metadata:
   resolution: "@types/tough-cookie@npm:4.0.2"
   checksum: e055556ffdaa39ad85ede0af192c93f93f986f4bd9e9426efdc2948e3e2632db3a4a584d4937dbf6d7620527419bc99e6182d3daf2b08685e710f2eda5291905
   languageName: node
   linkType: hard
 
+"@types/trusted-types@npm:*":
+  version: 2.0.2
+  resolution: "@types/trusted-types@npm:2.0.2"
+  checksum: 3371eef5f1c50e1c3c07a127c1207b262ba65b83dd167a1c460fc1b135a3fb0c97b9f508efebd383f239cc5dd5b7169093686a692a501fde9c3f7208657d9b0d
+  languageName: node
+  linkType: hard
+
 "@types/uglify-js@npm:*":
   version: 3.13.1
   resolution: "@types/uglify-js@npm:3.13.1"
   dependencies:
     source-map: ^0.6.1
@@ -18331,10 +18347,17 @@ __metadata:
   resolution: "dompurify@npm:2.3.8"
   checksum: dc7b32ee57a03fe5166a850071200897cc13fa069287a709e3b2138052d73ec09a87026b9e28c8d2f254a74eaa52ef30644e98e54294c30acbca2a53f1bbc5f4
   languageName: node
   linkType: hard
 
+"dompurify@npm:^2.4.1":
+  version: 2.4.3
+  resolution: "dompurify@npm:2.4.3"
+  checksum: b440981f2a38cada2085759cc3d1e2f94571afc34343d011a8a6aa1ad91ae6abf651adbfa4994b0e2283f0ce81f7891cdb04b67d0b234c8d190cb70e9691f026
+  languageName: node
+  linkType: hard
+
 "domutils@npm:^2.5.2, domutils@npm:^2.6.0":
   version: 2.8.0
   resolution: "domutils@npm:2.8.0"
   dependencies:
     dom-serializer: ^1.0.1
@@ -21663,10 +21686,11 @@ __metadata:
     "@types/common-tags": ^1.8.0
     "@types/d3": 7.4.0
     "@types/d3-force": ^2.1.0
     "@types/d3-scale-chromatic": 1.3.1
     "@types/debounce-promise": 3.1.5
+    "@types/dompurify": ^2
     "@types/eslint": 8.4.9
     "@types/file-saver": 2.0.5
     "@types/glob": ^8.0.0
     "@types/google.analytics": ^0.0.42
     "@types/gtag.js": ^0.0.12
@@ -21745,10 +21769,11 @@ __metadata:
     d3-force: ^2.1.1
     d3-scale-chromatic: 1.5.0
     dangerously-set-html-content: 1.0.9
     date-fns: 2.29.3
     debounce-promise: 3.1.2
+    dompurify: ^2.4.1
     emotion: 11.0.0
     esbuild: 0.16.17
     esbuild-loader: 2.21.0
     esbuild-plugin-browserslist: ^0.6.0
     eslint: 8.32.0

Vulnerable code:     const svgWid = name?.startsWith('gf-bar-align') ? 16 : name?.startsWith('gf-interp') ? 30 : svgSize;
import SVG from 'react-inlinesvg';
    <SVG
import SVG from 'react-inlinesvg';
        {mediaType === MediaType.Icon && <SVG src={file} className={styles.img} />}
import SVG from 'react-inlinesvg';
            <SVG src={card.imgUrl} className={styles.img} />
import SVG from 'react-inlinesvg';
      return <SVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />;
          prefix={sanitizedSrc && <SVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />}
import SVG from 'react-inlinesvg';
            {mediaType === MediaType.Icon && <SVG src={imgSrc} className={styles.img} />}
import InlineSVG from 'react-inlinesvg/esm';
                            <InlineSVG src={option.src} className={styles.optionSvg} />
import SVG from 'react-inlinesvg';
          <SVG src={src} className={styles.icon} />
import SVG from 'react-inlinesvg';
          <SVG
Fixed code:     "@types/dompurify": "^2",
    "dompurify": "^2.4.1",
    if (!name || name.includes('..')) {
      return <div ref={ref}>invalid icon name</div>;
    }

    const svgWid = name.startsWith('gf-bar-align') ? 16 : name.startsWith('gf-interp') ? 30 : svgSize;
import * as DOMPurify from 'dompurify';
import React from 'react';
import SVG, { Props } from 'react-inlinesvg';

export const SanitizedSVG = (props: Props) => {
  return <SVG {...props} cacheRequests={true} preProcessor={getCleanSVG} />;
};

let cache = new Map<string, string>();

function getCleanSVG(code: string): string {
  let clean = cache.get(code);
  if (!clean) {
    clean = DOMPurify.sanitize(code, { USE_PROFILES: { svg: true, svgFilters: true } });
    cache.set(code, clean);
  }
  return clean;
}
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
    <SanitizedSVG
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';

        {mediaType === MediaType.Icon && <SanitizedSVG src={file} className={styles.img} />}
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
            <SanitizedSVG src={card.imgUrl} className={styles.img} />
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
      return <SanitizedSVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />;
          prefix={sanitizedSrc && <SanitizedSVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />}
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
            {mediaType === MediaType.Icon && <SanitizedSVG src={imgSrc} className={styles.img} />}
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
                            <SanitizedSVG src={option.src} className={styles.optionSvg} />
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
          <SanitizedSVG src={src} className={styles.icon} />
import { SanitizedSVG } from 'app/core/components/SVG/SanitizedSVG';
          <SanitizedSVG
import * as DOMPurify from 'dompurify';
      text = DOMPurify.sanitize(text, { USE_PROFILES: { svg: true, svgFilters: true } });

"@types/dompurify@npm:^2":
  version: 2.4.0
  resolution: "@types/dompurify@npm:2.4.0"
  dependencies:
    "@types/trusted-types": "*"
  checksum: b48cd81e997794ebc390c7c5bef1a67ec14a6f2f0521973e07e06af186c7583abe114d94d24868c0632b9573f5bd77131a4b76f3fffdf089ba99a4e53dd46c39
  languageName: node
  linkType: hard

"@types/trusted-types@npm:*":
  version: 2.0.2
  resolution: "@types/trusted-types@npm:2.0.2"
  checksum: 3371eef5f1c50e1c3c07a127c1207b262ba65b83dd167a1c460fc1b135a3fb0c97b9f508efebd383f239cc5dd5b7169093686a692a501fde9c3f7208657d9b0d
  languageName: node
  linkType: hard

"dompurify@npm:^2.4.1":
  version: 2.4.3
  resolution: "dompurify@npm:2.4.3"
  checksum: b440981f2a38cada2085759cc3d1e2f94571afc34343d011a8a6aa1ad91ae6abf651adbfa4994b0e2283f0ce81f7891cdb04b67d0b234c8d190cb70e9691f026
  languageName: node
  linkType: hard

    "@types/dompurify": ^2
    dompurify: ^2.4.1