From 90501271edc3dd145ba52cdf363a67a92d4ddfde Mon Sep 17 00:00:00 2001 From: Piotr Date: Fri, 3 Apr 2020 07:15:51 +0200 Subject: [PATCH 001/248] Fix SVG output for Python3 fixes graphite-project/graphite-web#2518 --- webapp/graphite/render/glyph.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/webapp/graphite/render/glyph.py b/webapp/graphite/render/glyph.py index 2851321ab..a13adfb06 100644 --- a/webapp/graphite/render/glyph.py +++ b/webapp/graphite/render/glyph.py @@ -885,12 +885,12 @@ def output(self, fileObj): metaData = { } self.surface.finish() - svgData = str(self.surfaceData.getvalue()) + svgData = self.surfaceData.getvalue() self.surfaceData.close() - svgData = svgData.replace('pt"', 'px"', 2) # we expect height/width in pixels, not points - svgData = svgData.replace('\n', '', 1) - svgData = svgData.replace('\n\n\n', b'', 1) + svgData = svgData.replace(b'\n\n' % name - (svgData, subsMade) = re.subn(r'', onHeaderPath, svgData) + return b'' % name + (svgData, subsMade) = re.subn(b'', onHeaderPath, svgData) # Replace the first with , and close out the last at the end - svgData = svgData.replace(' 0: - svgData += "" - svgData = svgData.replace(' data-header="true"','') + svgData += b'' + svgData = svgData.replace(b' data-header="true"', b'') - fileObj.write(svgData.encode('utf-8')) + fileObj.write(svgData) fileObj.write(("""" + + for param in ('from', 'until'): + response = self.client.get(url, {'query': 'test', param: xssStr}) + self.assertXSS(response, status_code=400, msg_prefix='XSS detected in %s: ' % param) From 318e86339f81121c5ce4cbf83f43bd74d58af8b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:56:25 +0000 Subject: [PATCH 241/248] Initial plan From ef54ae04ce5f31cc43f989308fa55934f57bffee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:00:24 +0000 Subject: [PATCH 242/248] Fix reflected XSS in composer mygraph view (issue #2794) Co-authored-by: deniszh <1227222+deniszh@users.noreply.github.com> --- webapp/graphite/composer/views.py | 7 ++++--- webapp/tests/test_xss.py | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/webapp/graphite/composer/views.py b/webapp/graphite/composer/views.py index f60c37bf6..09f4f50c6 100644 --- a/webapp/graphite/composer/views.py +++ b/webapp/graphite/composer/views.py @@ -21,6 +21,7 @@ from django.http import HttpResponse from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from django.utils.html import escape def composer(request): @@ -65,7 +66,7 @@ def mygraph(request): newGraph.save() except Exception: log.exception("Failed to create new MyGraph in /composer/mygraph/, graphName=%s" % graphName) - return HttpResponse("Failed to save graph %s" % graphName) + return HttpResponse("Failed to save graph %s" % escape(graphName)) return HttpResponse("SAVED") @@ -75,9 +76,9 @@ def mygraph(request): existingGraph.delete() except ObjectDoesNotExist: - return HttpResponse("No such graph '%s'" % graphName) + return HttpResponse("No such graph '%s'" % escape(graphName)) return HttpResponse("DELETED") else: - return HttpResponse("Invalid operation '%s'" % action) + return HttpResponse("Invalid operation '%s'" % escape(action)) diff --git a/webapp/tests/test_xss.py b/webapp/tests/test_xss.py index da9d17eed..4b6cf8d54 100644 --- a/webapp/tests/test_xss.py +++ b/webapp/tests/test_xss.py @@ -6,6 +6,8 @@ except ImportError: # Django < 1.10 from django.urls import reverse +from django.contrib.auth.models import User + from .base import TestCase # Silence logging during tests @@ -49,3 +51,25 @@ def test_find_xss_script_tag(self): for param in ('from', 'until'): response = self.client.get(url, {'query': 'test', param: xssStr}) self.assertXSS(response, status_code=400, msg_prefix='XSS detected in %s: ' % param) + + +class ComposerMyGraphXSSTest(TestCase): + def setUp(self): + self.user = User.objects.create_user('testxss', 'testxss@example.com', 'pass') + self.client.login(username='testxss', password='pass') + + def test_mygraph_xss_action(self): + """Test that XSS in the action parameter is properly escaped (issue #2794)""" + url = reverse('composer_mygraph') + xssStr = '">' + + response = self.client.get(url, {'action': xssStr, 'graphName': 'test'}) + self.assertXSS(response, msg_prefix='XSS detected in action: ') + + def test_mygraph_xss_graphname(self): + """Test that XSS in the graphName parameter is properly escaped (issue #2794)""" + url = reverse('composer_mygraph') + xssStr = '">' + + response = self.client.get(url, {'action': 'delete', 'graphName': xssStr}) + self.assertXSS(response, msg_prefix='XSS detected in graphName: ') From 1ab65887cacd9fbeabb11d02533df0d1fb78b6ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:21:23 +0000 Subject: [PATCH 243/248] Initial plan From f4d931872957b7205e051ebf5858c6f3c20de005 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:29:11 +0000 Subject: [PATCH 244/248] Fix XSS vulnerability in Absolute Time Range date/time field validation tooltips Co-authored-by: deniszh <1227222+deniszh@users.noreply.github.com> --- webapp/content/js/dashboard.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/webapp/content/js/dashboard.js b/webapp/content/js/dashboard.js index e6ced3bcc..0d4fa0c18 100644 --- a/webapp/content/js/dashboard.js +++ b/webapp/content/js/dashboard.js @@ -1326,7 +1326,8 @@ function selectAbsoluteTime() { var startDateField = new Ext.form.DateField({ fieldLabel: 'Start Date', width: 125, - value: TimeRange.startDate || '' + value: TimeRange.startDate || '', + invalidText: 'Not a valid date' }); var startTimeField = new Ext.form.TimeField({ @@ -1334,13 +1335,15 @@ function selectAbsoluteTime() { width: 125, allowBlank: false, increment: 30, - value: TimeRange.startTime || '' + value: TimeRange.startTime || '', + invalidText: 'Not a valid time' }); var endDateField = new Ext.form.DateField({ fieldLabel: 'End Date', width: 125, - value: TimeRange.endDate || '' + value: TimeRange.endDate || '', + invalidText: 'Not a valid date' }); var endTimeField = new Ext.form.TimeField({ @@ -1348,7 +1351,8 @@ function selectAbsoluteTime() { width: 125, allowBlank: false, increment: 30, - value: TimeRange.endTime || '' + value: TimeRange.endTime || '', + invalidText: 'Not a valid time' }); var win; From 08726e89bdb90cb15311ec5e603930a37f671258 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:35:15 +0000 Subject: [PATCH 245/248] Initial plan From ddee7244bc76ab8fc1a14e88578169b7badb82e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 22:39:44 +0000 Subject: [PATCH 246/248] Fix: Remove single-to-double quote replacement in dashboard syncGraphs function Co-authored-by: deniszh <1227222+deniszh@users.noreply.github.com> --- webapp/content/js/dashboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/content/js/dashboard.js b/webapp/content/js/dashboard.js index 0d4fa0c18..a6f3d9e62 100644 --- a/webapp/content/js/dashboard.js +++ b/webapp/content/js/dashboard.js @@ -1875,7 +1875,7 @@ function graphClicked(graphView, graphIndex, element, evt) { function syncGraphs(thisStore, record, operation) { var targets = []; - thisStore.each(function (rec) { targets.push(rec.data.target.replace(/'/g, '"')); }); + thisStore.each(function (rec) { targets.push(rec.data.target); }); selectedRecord.data.params.target = targets; selectedRecord.data.target = Ext.urlEncode({target: targets}); refreshGraphs(); From eb9f05b0ac19ec208d309730539e9554807b232c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Pazos?= Date: Mon, 30 Mar 2026 14:58:59 -0300 Subject: [PATCH 247/248] Fix TypeError in _nonNegativeDelta when val is None and maxValue is set Move the None check before the maxValue/minValue comparisons to avoid comparing None against a float/int. --- webapp/graphite/render/functions.py | 8 ++++---- webapp/tests/test_functions.py | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/webapp/graphite/render/functions.py b/webapp/graphite/render/functions.py index 8630feb8f..b0e2dab91 100644 --- a/webapp/graphite/render/functions.py +++ b/webapp/graphite/render/functions.py @@ -2427,16 +2427,16 @@ def nonNegativeDerivative(requestContext, seriesList, maxValue=None, minValue=No def _nonNegativeDelta(val, prev, maxValue, minValue): + # first reading or None value + if None in (prev, val): + return None, val + # ignore values larger than maxValue if maxValue is not None and val > maxValue: return None, None if minValue is not None and val < minValue: return None, None - # first reading - if None in (prev, val): - return None, val - # counter increased, use the difference if val >= prev: return val - prev, val diff --git a/webapp/tests/test_functions.py b/webapp/tests/test_functions.py index cf3fce468..56f7dd959 100644 --- a/webapp/tests/test_functions.py +++ b/webapp/tests/test_functions.py @@ -1798,6 +1798,10 @@ def test_perSecond_nones(self): result = functions.scaleToSeconds({}, functions.nonNegativeDerivative({}, seriesList), 1) self.assertEqual(list(expected[0]), list(result[0])) + # None values should not raise TypeError when maxValue is set + result = functions.perSecond({}, seriesList, maxValue=1000) + self.assertEqual(list(expected[0]), list(result[0])) + def test_perSecond_max(self): seriesList = self._gen_series_list_with_data(key='test',start=0,end=600,step=60,data=[0, 120, 240, 480, 960, 900, 120, 240, 119, 479]) expected = [TimeSeries('perSecond(test)', 0, 600, 60, [None, 2, 2, 4, None, None, None, 2, 6, 6])] From f86f943e745ebd631a3ab320eda8602219c75f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Pazos?= Date: Mon, 30 Mar 2026 17:22:41 -0300 Subject: [PATCH 248/248] preserve original behaviour --- webapp/graphite/render/functions.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/webapp/graphite/render/functions.py b/webapp/graphite/render/functions.py index b0e2dab91..329149ff9 100644 --- a/webapp/graphite/render/functions.py +++ b/webapp/graphite/render/functions.py @@ -2427,9 +2427,8 @@ def nonNegativeDerivative(requestContext, seriesList, maxValue=None, minValue=No def _nonNegativeDelta(val, prev, maxValue, minValue): - # first reading or None value - if None in (prev, val): - return None, val + if val is None: + return None, None # ignore values larger than maxValue if maxValue is not None and val > maxValue: @@ -2437,6 +2436,10 @@ def _nonNegativeDelta(val, prev, maxValue, minValue): if minValue is not None and val < minValue: return None, None + # first reading + if prev is None: + return None, val + # counter increased, use the difference if val >= prev: return val - prev, val