diff mbox series

[layerindex-web,3/4] layerindex/views: don't expose patch generation errors to users

Message ID 20261020205730.53370-4-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
bulk_change_patch_view returned the output of the failing bulkchange.py
run, or the exception text, in the HTTP response. That can include
tracebacks, server paths and bitbake output. Log the details instead
and return a generic error with status 500. The lock timeout message
is kept, since it tells the user what to do and reveals nothing.

Reported by CodeQL (py/stack-trace-exposure).

AI-Generated: Claude Opus 5.5
Signed-off-by: Tim Orling <tim.orling@konsulko.com>
---
 layerindex/views.py             | 14 ++++++++------
 tests/test_bulkchange_access.py | 11 +++++++++++
 2 files changed, 19 insertions(+), 6 deletions(-)
diff mbox series

Patch

diff --git a/layerindex/views.py b/layerindex/views.py
index 42722bf..592311f 100644
--- a/layerindex/views.py
+++ b/layerindex/views.py
@@ -6,6 +6,7 @@ 
 #
 # SPDX-License-Identifier: MIT
 
+import logging
 import os
 import sys
 import re
@@ -63,6 +64,8 @@  from layerindex.models import (BBAppend, BBClass, BBClassGlobal, BBClassRecipe,
 
 from . import tasks, utils
 
+logger = logging.getLogger(__name__)
+
 def edit_layernote_view(request, template_name, slug, pk=None):
     layeritem = get_object_or_404(LayerItem, name=slug)
     if layeritem.comparison:
@@ -347,12 +350,11 @@  def bulk_change_patch_view(request, pk):
         return HttpResponse('No patch data generated', content_type='text/plain')
     except Exception as e:
         output = getattr(e, 'output', None)
-        if output:
-            if 'timeout' in output:
-                return HttpResponse('Failed to generate patches: timed out waiting for lock. Please try again shortly.', content_type='text/plain')
-            else:
-                return HttpResponse('Failed to generate patches: %s' % output, content_type='text/plain')
-        return HttpResponse('Failed to generate patches: %s' % e, content_type='text/plain')
+        if output and 'timeout' in output:
+            return HttpResponse('Failed to generate patches: timed out waiting for lock. Please try again shortly.', content_type='text/plain')
+        # Details can include paths and bitbake output, so keep them in the server log
+        logger.exception('Failed to generate patches for bulk change %s: %s', pk, output)
+        return HttpResponse('Failed to generate patches, please contact the site administrator.', content_type='text/plain', status=500)
     # FIXME better error handling
 
 
diff --git a/tests/test_bulkchange_access.py b/tests/test_bulkchange_access.py
index e85451d..914493c 100644
--- a/tests/test_bulkchange_access.py
+++ b/tests/test_bulkchange_access.py
@@ -51,3 +51,14 @@  class TestBulkChangeAccess(TestCase):
         for name in ('bulk_change_edit', 'bulk_change_review'):
             response = self.client.get(self._url(name))
             self.assertEqual(response.status_code, 200, name)
+
+    def test_patch_error_details_not_exposed(self):
+        import subprocess
+        from unittest import mock
+        self.client.force_login(self.owner)
+        err = subprocess.CalledProcessError(1, 'bulkchange.py', output='Traceback: /opt/secret/path')
+        with mock.patch('layerindex.views.utils.runcmd', side_effect=err), \
+                self.assertLogs('layerindex.views', 'ERROR'):
+            response = self.client.get(self._url('bulk_change_patches'))
+        self.assertEqual(response.status_code, 500)
+        self.assertNotIn(b'/opt/secret/path', response.content)