diff --git a/package.json b/package.json index c25061fec70..ab451c78e39 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "@types/jquery": "3.3.38", "@types/lodash": "4.14.149", "@types/lru-cache": "^5.1.0", - "@types/marked": "1.1.0", "@types/moment-timezone": "0.5.13", "@types/mousetrap": "1.6.3", "@types/node": "13.7.0", @@ -247,7 +246,6 @@ "jsurl": "^0.1.5", "lodash": "4.17.19", "lru-cache": "^5.1.1", - "marked": "1.1.1", "md5": "^2.2.1", "memoize-one": "5.1.1", "moment": "2.24.0", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index f8d8a54c759..a72437fc971 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -28,6 +28,7 @@ "eventemitter3": "4.0.7", "lodash": "4.17.19", "rxjs": "6.6.3", + "marked": "1.2.2", "xss": "1.0.6" }, "devDependencies": { @@ -40,6 +41,7 @@ "@types/jquery": "3.3.38", "@types/lodash": "4.14.123", "@types/node": "10.14.1", + "@types/marked": "1.1.0", "@types/papaparse": "5.2.0", "@types/react": "16.8.16", "@types/rollup-plugin-visualizer": "2.6.0", diff --git a/packages/grafana-data/src/text/markdown.test.ts b/packages/grafana-data/src/text/markdown.test.ts index 3df231c77d4..ed51f2c28a2 100644 --- a/packages/grafana-data/src/text/markdown.test.ts +++ b/packages/grafana-data/src/text/markdown.test.ts @@ -5,4 +5,9 @@ describe('Markdown wrapper', () => { const str = renderMarkdown(undefined); expect(str).toBe(''); }); + + it('should sanitize by default', () => { + const str = renderMarkdown(''); + expect(str).toBe('<script>alert()</script>'); + }); }); diff --git a/packages/grafana-data/src/text/markdown.ts b/packages/grafana-data/src/text/markdown.ts index f3c8d2f8981..4c9b369e291 100644 --- a/packages/grafana-data/src/text/markdown.ts +++ b/packages/grafana-data/src/text/markdown.ts @@ -1,19 +1,28 @@ -import marked, { MarkedOptions } from 'marked'; +import marked from 'marked'; +import { sanitize } from './sanitize'; -const defaultMarkedOptions: MarkedOptions = { - renderer: new marked.Renderer(), - pedantic: false, - gfm: true, - sanitize: true, - smartLists: true, - smartypants: false, - xhtml: false, -}; +let hasInitialized = false; -export function setMarkdownOptions(optionsOverride?: MarkedOptions) { - marked.setOptions({ ...defaultMarkedOptions, ...optionsOverride }); +export interface RenderMarkdownOptions { + noSanitize?: boolean; } -export function renderMarkdown(str?: string): string { - return marked(str || ''); +export function renderMarkdown(str?: string, options?: RenderMarkdownOptions): string { + if (!hasInitialized) { + marked.setOptions({ + pedantic: false, + gfm: true, + smartLists: true, + smartypants: false, + xhtml: false, + }); + hasInitialized = true; + } + + const html = marked(str || ''); + if (options?.noSanitize) { + return html; + } + + return sanitize(html); } diff --git a/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx b/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx index 235b1007c91..aa1f523542a 100644 --- a/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx +++ b/packages/grafana-ui/src/components/Typeahead/TypeaheadInfo.tsx @@ -2,7 +2,7 @@ import React, { useContext } from 'react'; import { css, cx } from 'emotion'; import { CompletionItem, selectThemeVariant, ThemeContext } from '../..'; -import { GrafanaTheme, renderMarkdown, textUtil } from '@grafana/data'; +import { GrafanaTheme, renderMarkdown } from '@grafana/data'; const getStyles = (theme: GrafanaTheme, height: number, visible: boolean) => { return { @@ -41,7 +41,7 @@ interface Props { export const TypeaheadInfo: React.FC = ({ item, height }) => { const visible = item && !!item.documentation; const label = item ? item.label : ''; - const documentation = textUtil.sanitize(renderMarkdown(item?.documentation)); + const documentation = renderMarkdown(item?.documentation); const theme = useContext(ThemeContext); const styles = getStyles(theme, height, visible); diff --git a/public/app/app.ts b/public/app/app.ts index 27d338def7a..3981ad7ef1c 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -28,7 +28,6 @@ import _ from 'lodash'; import { AppEvents, setLocale, - setMarkdownOptions, standardEditorsRegistry, standardFieldConfigEditorRegistry, standardTransformersRegistry, @@ -97,8 +96,6 @@ export class GrafanaApp { setLocale(config.bootData.user.locale); setTimeZoneResolver(() => config.bootData.user.timezone); - setMarkdownOptions({ sanitize: !config.disableSanitizeHtml }); - standardEditorsRegistry.setInit(getStandardOptionEditors); standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs); standardTransformersRegistry.setInit(getStandardTransformers); diff --git a/public/app/plugins/panel/text/TextPanel.tsx b/public/app/plugins/panel/text/TextPanel.tsx index 5a7d374bdbe..de472f1c62c 100644 --- a/public/app/plugins/panel/text/TextPanel.tsx +++ b/public/app/plugins/panel/text/TextPanel.tsx @@ -43,7 +43,8 @@ export class TextPanel extends PureComponent { } prepareMarkdown(content: string): string { - return renderMarkdown(this.interpolateAndSanitizeString(content)); + // Sanitize is disabled here as we handle that after variable interpolation + return renderMarkdown(this.interpolateAndSanitizeString(content), { noSanitize: config.disableSanitizeHtml }); } interpolateAndSanitizeString(content: string): string { diff --git a/yarn.lock b/yarn.lock index 0ea90d6aa93..abcb1a9be63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18229,10 +18229,10 @@ markdown-to-jsx@^6.11.4: prop-types "^15.6.2" unquote "^1.1.0" -marked@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/marked/-/marked-1.1.1.tgz#e5d61b69842210d5df57b05856e0c91572703e6a" - integrity sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw== +marked@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.2.tgz#5d77ffb789c4cb0ae828bfe76250f7140b123f70" + integrity sha512-5jjKHVl/FPo0Z6ocP3zYhKiJLzkwJAw4CZoLjv57FkvbUuwOX4LIBBGGcXjAY6ATcd1q9B8UTj5T9Umauj0QYQ== marked@^0.3.12: version "0.3.19"