diff --git a/package.json b/package.json index 43563833fa3..f77c0b94410 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,7 @@ "@types/d3-force": "^2.1.0", "@types/d3-scale-chromatic": "1.3.1", "@types/debounce-promise": "3.1.4", + "@types/dompurify": "^2", "@types/enzyme": "3.10.11", "@types/enzyme-adapter-react-16": "1.0.6", "@types/eslint": "8.4.1", @@ -306,6 +307,7 @@ "dangerously-set-html-content": "1.0.9", "date-fns": "2.28.0", "debounce-promise": "3.1.2", + "dompurify": "^2.4.1", "emotion": "11.0.0", "eventemitter3": "4.0.7", "fast-deep-equal": "^3.1.3", 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 ; +}; + +let cache = new Map(); + +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 ea785302161..9f3513b47aa 100644 --- a/public/app/features/canvas/elements/icon.tsx +++ b/public/app/features/canvas/elements/icon.tsx @@ -1,8 +1,8 @@ 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, @@ -57,7 +57,7 @@ export function IconDisplay(props: CanvasElementProps) { }; return ( - onChange(card.value)} > {card.imgUrl.endsWith('.svg') ? ( - + ) : ( )} diff --git a/public/app/features/dimensions/editors/ResourcePicker.tsx b/public/app/features/dimensions/editors/ResourcePicker.tsx index 7bc55df26b7..06cc2b89bbb 100644 --- a/public/app/features/dimensions/editors/ResourcePicker.tsx +++ b/public/app/features/dimensions/editors/ResourcePicker.tsx @@ -1,6 +1,5 @@ import { css } from '@emotion/css'; import React, { createRef } from 'react'; -import SVG from 'react-inlinesvg'; import { GrafanaTheme2 } from '@grafana/data'; import { @@ -15,6 +14,7 @@ import { 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'; @@ -56,7 +56,7 @@ export const ResourcePicker = (props: Props) => { const renderSmallResourcePicker = () => { if (value && sanitizedSrc) { - return ; + return ; } else { return ( @@ -73,7 +73,7 @@ export const ResourcePicker = (props: Props) => { value={name} placeholder={placeholder} readOnly={true} - prefix={sanitizedSrc && } + prefix={sanitizedSrc && } suffix={