diff --git a/conf/defaults.ini b/conf/defaults.ini index 7f61ac96870..b0de259de19 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -570,6 +570,7 @@ callback_url = [panels] enable_alpha = false +disable_sanitize_input = false [enterprise] license_path = diff --git a/package.json b/package.json index 470101ff0c4..ec6420564eb 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,8 @@ "slate-react": "^0.12.4", "tether": "^1.4.0", "tether-drop": "https://github.com/torkelo/drop/tarball/master", - "tinycolor2": "^1.4.1" + "tinycolor2": "^1.4.1", + "xss": "^1.0.3" }, "resolutions": { "caniuse-db": "1.0.30000772", diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 6d6cc708496..adf7e83325e 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -166,6 +166,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf "externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl, "externalUserMngLinkName": setting.ExternalUserMngLinkName, "viewersCanEdit": setting.ViewersCanEdit, + "disableSanitizeInput": hs.Cfg.DisableSanitizeInput, "buildInfo": map[string]interface{}{ "version": setting.BuildVersion, "commit": setting.BuildCommit, diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 1417392fdf8..1f9db6fbb78 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -18,7 +18,7 @@ import ( "github.com/go-macaron/session" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/util" - "gopkg.in/ini.v1" + ini "gopkg.in/ini.v1" ) type Scheme string @@ -90,6 +90,7 @@ var ( EmailCodeValidMinutes int DataProxyWhiteList map[string]bool DisableBruteForceLoginProtection bool + DisableSanitizeInput bool // Snapshots ExternalSnapshotUrl string @@ -222,6 +223,7 @@ type Cfg struct { MetricsEndpointBasicAuthUsername string MetricsEndpointBasicAuthPassword string EnableAlphaPanels bool + DisableSanitizeInput bool EnterpriseLicensePath string } @@ -709,6 +711,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { panels := iniFile.Section("panels") cfg.EnableAlphaPanels = panels.Key("enable_alpha").MustBool(false) + cfg.DisableSanitizeInput = panels.Key("sanitize_input_disabled").MustBool(false) cfg.readSessionConfig() cfg.readSmtpSettings() diff --git a/public/app/core/config.ts b/public/app/core/config.ts index 26f31ffcf54..ce7f0fcfe50 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -35,8 +35,9 @@ export class Settings { loginHint: any; loginError: any; viewersCanEdit: boolean; + disableSanitizeInput: boolean; - constructor(options) { + constructor(options: Settings) { const defaults = { datasources: {}, windowTitlePrefix: 'Grafana - ', @@ -52,6 +53,7 @@ export class Settings { isEnterprise: false, }, viewersCanEdit: false, + disableSanitizeInput: false }; _.extend(this, defaults, options); diff --git a/public/app/core/utils/text.ts b/public/app/core/utils/text.ts index 4e948116dba..9f4f1c41716 100644 --- a/public/app/core/utils/text.ts +++ b/public/app/core/utils/text.ts @@ -1,4 +1,5 @@ import { TextMatch } from 'app/types/explore'; +import xss from 'xss'; /** * Adapt findMatchesInText for react-highlight-words findChunks handler. @@ -22,7 +23,7 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[] } const matches = []; const cleaned = cleanNeedle(needle); - let regexp; + let regexp: RegExp; try { regexp = new RegExp(`(?:${cleaned})`, 'g'); } catch (error) { @@ -42,3 +43,12 @@ export function findMatchesInText(haystack: string, needle: string): TextMatch[] }); return matches; } + +export function sanitize (unsanitizedString: string): string { + try { + return xss(unsanitizedString); + } catch (error) { + console.log('String could not be sanitized', unsanitizedString); + return unsanitizedString; + } +} diff --git a/public/app/plugins/panel/text/module.ts b/public/app/plugins/panel/text/module.ts index 08ab4cd2b96..7d2a0ab0dd0 100644 --- a/public/app/plugins/panel/text/module.ts +++ b/public/app/plugins/panel/text/module.ts @@ -1,6 +1,8 @@ import _ from 'lodash'; import { PanelCtrl } from 'app/plugins/sdk'; import Remarkable from 'remarkable'; +import { sanitize } from 'app/core/utils/text'; +import config from 'app/core/config'; const defaultContent = ` # Title @@ -33,11 +35,19 @@ export class TextPanelCtrl extends PanelCtrl { this.events.on('refresh', this.onRefresh.bind(this)); this.events.on('render', this.onRender.bind(this)); + const renderWhenChanged = (scope: any) => { + const { panel } = scope.ctrl; + return [ + panel.content, + panel.mode + ].join(); + }; + $scope.$watch( - 'ctrl.panel.content', + renderWhenChanged, _.throttle(() => { this.render(); - }, 1000) + }, 100) ); } @@ -62,7 +72,7 @@ export class TextPanelCtrl extends PanelCtrl { this.renderingCompleted(); } - renderText(content) { + renderText(content: string) { content = content .replace(/&/g, '&') .replace(/>/g, '>') @@ -71,7 +81,7 @@ export class TextPanelCtrl extends PanelCtrl { this.updateContent(content); } - renderMarkdown(content) { + renderMarkdown(content: string) { if (!this.remarkable) { this.remarkable = new Remarkable(); } @@ -81,7 +91,9 @@ export class TextPanelCtrl extends PanelCtrl { }); } - updateContent(html) { + updateContent(html: string) { + const { disableSanitizeInput } = config; + html = disableSanitizeInput ? html : sanitize(html); try { this.content = this.$sce.trustAsHtml(this.templateSrv.replace(html, this.panel.scopedVars)); } catch (e) { diff --git a/yarn.lock b/yarn.lock index 62a059cffec..25467969e35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3560,6 +3560,11 @@ cssesc@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" +cssfilter@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" + integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4= + cssnano@^3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" @@ -13344,6 +13349,14 @@ xregexp@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" +xss@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.3.tgz#d04bd2558fd6c29c46113824d5e8b2a910054e23" + integrity sha512-LTpz3jXPLUphMMmyufoZRSKnqMj41OVypZ8uYGzvjkMV9C1EdACrhQl/EM8Qfh5htSAuMIQFOejmKAZGkJfaCg== + dependencies: + commander "^2.9.0" + cssfilter "0.0.10" + xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"