diff mbox series

[layerindex-web,1/4] layerindex/views: fix reflected XSS in recipes CSV export

Message ID 20261020205730.53370-2-tim.orling@konsulko.com
State New
Headers show
Series Security fixes: XSS, bulk change access checks, error disclosure | expand

Commit Message

Tim Orling Oct. 20, 2026, 8:57 p.m. UTC
layer_export_recipes_csv_view echoed an invalid name from the "fields"
query parameter back in an HttpResponse with the default text/html
content type, so a crafted link could run script on the site without
the victim being logged in. Return the error as text/plain with status
400 instead.

Reported by CodeQL (py/reflective-xss).

AI-Generated: Claude Opus 5.5
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
 layerindex/views.py       |  2 +-
 tests/test_recipes_csv.py | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 34 insertions(+), 1 deletion(-)
 create mode 100644 tests/test_recipes_csv.py
diff mbox series

Patch

diff --git a/layerindex/views.py b/layerindex/views.py
index dea0a98..c030ae1 100644
--- a/layerindex/views.py
+++ b/layerindex/views.py
@@ -1599,7 +1599,7 @@  def layer_export_recipes_csv_view(request, branch, slug):
     recipe_fields = [f.name for f in Recipe._meta.get_fields() if not (f.auto_created and f.is_relation)]
     for field in fieldlist:
         if field not in recipe_fields:
-            return HttpResponse('Field %s is invalid' % field)
+            return HttpResponse('Field %s is invalid' % field, content_type='text/plain', status=400)
 
     writer = csv.writer(response)
     for recipe in layerbranch.sorted_recipes():
diff --git a/tests/test_recipes_csv.py b/tests/test_recipes_csv.py
new file mode 100644
index 0000000..9ed9084
--- /dev/null
+++ b/tests/test_recipes_csv.py
@@ -0,0 +1,33 @@ 
+# layerindex-web - tests for the layer recipes CSV export
+#
+# Licensed under the MIT license, see COPYING.MIT for details
+#
+# SPDX-License-Identifier: MIT
+
+import pytest
+from django.test import TestCase
+from django.urls import reverse
+
+from layerindex.models import Branch, LayerItem, LayerBranch
+
+
+@pytest.mark.django_db
+class TestRecipesCsv(TestCase):
+
+    def setUp(self):
+        branch = Branch.objects.create(name='master', bitbake_branch='master')
+        layer = LayerItem.objects.create(name='meta-test', status='P', layer_type='A',
+                                         summary='test', description='test',
+                                         vcs_url='git://example.com/meta-test.git')
+        LayerBranch.objects.create(layer=layer, branch=branch)
+        self.url = reverse('layer_export_recipes_csv', args=('master', 'meta-test'))
+
+    def test_valid_fields(self):
+        response = self.client.get(self.url, {'fields': 'pn,pv'})
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(response['Content-Type'].startswith('text/csv'))
+
+    def test_invalid_field_not_reflected_as_html(self):
+        response = self.client.get(self.url, {'fields': '<script>alert(1)</script>'})
+        self.assertEqual(response.status_code, 400)
+        self.assertTrue(response['Content-Type'].startswith('text/plain'))