diff --git a/package.json b/package.json index f6ea4315f5b..d3c52789b00 100644 --- a/package.json +++ b/package.json @@ -202,6 +202,7 @@ }, "dependencies": { "@babel/polyfill": "7.6.0", + "@braintree/sanitize-url": "4.0.0", "@grafana/slate-react": "0.22.9-grafana", "@torkelo/react-select": "2.4.1", "@types/react-loadable": "5.5.2", diff --git a/public/app/core/config.ts b/public/app/core/config.ts index ff5b31a005f..281314c4c8d 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -1,5 +1,18 @@ import { config, GrafanaBootConfig } from '@grafana/runtime'; - // Legacy binding paths export { config, GrafanaBootConfig as Settings }; -export default config; + +let grafanaConfig: GrafanaBootConfig = config; + +export default grafanaConfig; + +export const getConfig = () => { + return grafanaConfig; +}; + +export const updateConfig = (update: Partial) => { + grafanaConfig = { + ...grafanaConfig, + ...update, + }; +}; diff --git a/public/app/core/utils/text.ts b/public/app/core/utils/text.ts index 2cff89f8c7e..c77a7b8fa90 100644 --- a/public/app/core/utils/text.ts +++ b/public/app/core/utils/text.ts @@ -1,4 +1,5 @@ import xss from 'xss'; +import { sanitizeUrl as braintreeSanitizeUrl } from '@braintree/sanitize-url'; const XSSWL = Object.keys(xss.whiteList).reduce((acc, element) => { // @ts-ignore @@ -26,6 +27,10 @@ export function sanitize(unsanitizedString: string): string { } } +export function sanitizeUrl(url: string): string { + return braintreeSanitizeUrl(url); +} + export function hasAnsiCodes(input: string): boolean { return /\u001b\[\d{1,2}m/.test(input); } diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 97d60c960c6..ce9813fae99 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -276,11 +276,10 @@ export class PanelCtrl { let html = '
'; const md = renderMarkdown(interpolatedMarkdown); - html += config.disableSanitizeHtml ? md : sanitize(md); + html += md; if (panel.links && panel.links.length > 0) { const interpolatedLinks = getPanelLinksSupplier(panel).getLinks(); - html += '
'; - return html; + return config.disableSanitizeHtml ? html : sanitize(html); } // overriden from react diff --git a/public/app/features/panel/panellinks/linkSuppliers.test.ts b/public/app/features/panel/panellinks/linkSuppliers.test.ts index 5a4f97dd477..85f1ea45932 100644 --- a/public/app/features/panel/panellinks/linkSuppliers.test.ts +++ b/public/app/features/panel/panellinks/linkSuppliers.test.ts @@ -32,11 +32,11 @@ describe('getLinksFromLogsField', () => { links: [ { title: 'title1', - url: 'domain.com/${__value.raw}', + url: 'http://domain.com/${__value.raw}', }, { title: 'title2', - url: 'anotherdomain.sk/${__value.raw}', + url: 'http://anotherdomain.sk/${__value.raw}', }, ], }, @@ -44,8 +44,8 @@ describe('getLinksFromLogsField', () => { }; const links = getLinksFromLogsField(field, 2); expect(links.length).toBe(2); - expect(links[0].href).toBe('domain.com/3'); - expect(links[1].href).toBe('anotherdomain.sk/3'); + expect(links[0].href).toBe('http://domain.com/3'); + expect(links[1].href).toBe('http://anotherdomain.sk/3'); }); it('handles zero links', () => { diff --git a/public/app/features/panel/panellinks/link_srv.ts b/public/app/features/panel/panellinks/link_srv.ts index 7801f9f7062..c35f645a388 100644 --- a/public/app/features/panel/panellinks/link_srv.ts +++ b/public/app/features/panel/panellinks/link_srv.ts @@ -3,6 +3,8 @@ import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import templateSrv, { TemplateSrv } from 'app/features/templating/template_srv'; import coreModule from 'app/core/core_module'; import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url'; +import { sanitizeUrl } from 'app/core/utils/text'; +import { getConfig } from 'app/core/config'; import { VariableSuggestion, VariableOrigin, DataLinkBuiltInVars } from '@grafana/ui'; import { DataLink, KeyValue, deprecationWarning, LinkModel, DataFrame, ScopedVars } from '@grafana/data'; @@ -209,7 +211,7 @@ export class LinkSrv implements LinkService { value: variablesQuery, }, }); - + info.href = getConfig().disableSanitizeHtml ? info.href : sanitizeUrl(info.href); return info; }; diff --git a/public/app/features/panel/panellinks/specs/link_srv.test.ts b/public/app/features/panel/panellinks/specs/link_srv.test.ts index 6e3daaf4a65..37274110779 100644 --- a/public/app/features/panel/panellinks/specs/link_srv.test.ts +++ b/public/app/features/panel/panellinks/specs/link_srv.test.ts @@ -4,6 +4,7 @@ import _ from 'lodash'; import { TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { TemplateSrv } from 'app/features/templating/template_srv'; import { advanceTo } from 'jest-date-mock'; +import { updateConfig } from '../../../../core/config'; jest.mock('angular', () => { const AngularJSMock = require('test/mocks/angular'); @@ -137,4 +138,36 @@ describe('linkSrv', () => { ).toEqual('/d/1?time=1000000001'); }); }); + + describe('sanitization', () => { + const url = "javascript:alert('broken!);"; + it.each` + disableSanitizeHtml | expected + ${true} | ${url} + ${false} | ${'about:blank'} + `( + "when disable disableSanitizeHtml set to '$disableSanitizeHtml' then result should be '$expected'", + ({ disableSanitizeHtml, expected }) => { + updateConfig({ + disableSanitizeHtml, + }); + + const link = linkSrv.getDataLinkUIModel( + { + title: 'Any title', + url, + }, + { + __value: { + value: { time: dataPointMock.datapoint[0] }, + text: 'Value', + }, + }, + {} + ).href; + + expect(link).toBe(expected); + } + ); + }); }); diff --git a/public/app/plugins/datasource/loki/configuration/DebugSections.test.tsx b/public/app/plugins/datasource/loki/configuration/DebugSections.test.tsx index abcdfd9cf38..9ee7795fc4b 100644 --- a/public/app/plugins/datasource/loki/configuration/DebugSections.test.tsx +++ b/public/app/plugins/datasource/loki/configuration/DebugSections.test.tsx @@ -46,7 +46,7 @@ describe('DebugSection', () => { { matcherRegex: 'traceId=(\\w+)', name: 'traceIdLink', - url: 'localhost/trace/${__value.raw}', + url: 'http://localhost/trace/${__value.raw}', }, { matcherRegex: 'traceId=(\\w+)', @@ -70,7 +70,7 @@ describe('DebugSection', () => { wrapper .find('tr') .at(1) - .contains('localhost/trace/1234') + .contains('http://localhost/trace/1234') ).toBeTruthy(); }); }); diff --git a/public/app/types/sanitize-url.d.ts b/public/app/types/sanitize-url.d.ts new file mode 100644 index 00000000000..9f6af49c606 --- /dev/null +++ b/public/app/types/sanitize-url.d.ts @@ -0,0 +1,3 @@ +declare module '@braintree/sanitize-url' { + function sanitizeUrl(url: string): string; +} diff --git a/yarn.lock b/yarn.lock index a6d43b4b696..bd8379f5467 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1665,6 +1665,11 @@ resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047" integrity sha512-4Th98KlMHr5+JkxfcoDT//6vY8vM+iSPrLNpHhRyLx2CFYi8e2RfqPLdpbnpo0Q5lQC5hNB79yes07zb02fvCw== +"@braintree/sanitize-url@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-4.0.0.tgz#2cda79ffd67b6ea919a63b5e1a883b92d636e844" + integrity sha512-bOoFoTxuEUuri/v1q0OXN0HIrZ2EiZlRSKdveU8vS5xf2+g0TmpXhmxkTc1s+XWR5xZNoVU4uvf/Mher98tfLw== + "@cnakazawa/watch@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" @@ -8567,7 +8572,7 @@ debug@^0.7.2: resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" integrity sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk= -debuglog@*, debuglog@^1.0.1: +debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -11829,7 +11834,7 @@ import-local@^2.0.0: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" -imurmurhash@*, imurmurhash@^0.1.4: +imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -13825,11 +13830,6 @@ lodash-es@^4.2.1: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== -lodash._baseindexof@*: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" - integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= - lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -13838,29 +13838,12 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@*: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" - integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= - -lodash._cacheindexof@*: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" - integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= - -lodash._createcache@*: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" - integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= - dependencies: - lodash._getnative "^3.0.0" - lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@*, lodash._getnative@^3.0.0: +lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= @@ -13964,11 +13947,6 @@ lodash.padend@^4.6.1: resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e" integrity sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4= -lodash.restparam@*: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"