From fe09228d85bf3b4f2b3411b42a0b17e45c2a378f Mon Sep 17 00:00:00 2001 From: MattMRE <160173637+mattmre@users.noreply.github.com> Date: Thu, 24 Sep 2026 08:47:11 -0400 Subject: [PATCH] fix(dashboard): reject non-integer limits on history routes Campaign, validation, preflight, and evidence-chain history used to ignore a bad limit and return the default page. They now return HTTP 400, the same rule as /api/events. --- dashboard_server.py | 20 ++++++++++++-------- test_dashboard_api_limits.py | 12 ++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dashboard_server.py b/dashboard_server.py index cb67155..8ebb8cc 100644 --- a/dashboard_server.py +++ b/dashboard_server.py @@ -1920,9 +1920,10 @@ def handle_api_campaign_history(self, query_params: Dict[str, List[str]]): if "limit" in query_params: try: limit = int(query_params["limit"][0]) - limit = min(limit, _MAX_API_LIMIT) except (ValueError, IndexError): - limit = 25 + self.send_error_response(400, "limit must be an integer") + return + limit = min(limit, _MAX_API_LIMIT) try: self.send_json_response(load_campaign_history(CAMPAIGN_HISTORY_ROOT, limit=limit)) except Exception: @@ -1934,9 +1935,10 @@ def handle_api_validation_history(self, query_params: Dict[str, List[str]]): if "limit" in query_params: try: limit = int(query_params["limit"][0]) - limit = min(limit, _MAX_API_LIMIT) except (ValueError, IndexError): - limit = 10 + self.send_error_response(400, "limit must be an integer") + return + limit = min(limit, _MAX_API_LIMIT) try: self.send_json_response(load_validation_history(VALIDATION_HISTORY_ROOT, limit=limit)) except Exception: @@ -1948,9 +1950,10 @@ def handle_api_preflight_history(self, query_params: Dict[str, List[str]]): if "limit" in query_params: try: limit = int(query_params["limit"][0]) - limit = min(limit, _MAX_API_LIMIT) except (ValueError, IndexError): - limit = 10 + self.send_error_response(400, "limit must be an integer") + return + limit = min(limit, _MAX_API_LIMIT) try: self.send_json_response(load_preflight_history(PREFLIGHT_HISTORY_ROOT, limit=limit)) except Exception: @@ -1969,9 +1972,10 @@ def handle_api_evidence_chain_history(self, query_params: Dict[str, List[str]]): if "limit" in query_params: try: limit = int(query_params["limit"][0]) - limit = min(limit, _MAX_API_LIMIT) except (ValueError, IndexError): - limit = 10 + self.send_error_response(400, "limit must be an integer") + return + limit = min(limit, _MAX_API_LIMIT) try: self.send_json_response(load_evidence_chain_history(EVIDENCE_CHAIN_HISTORY_ROOT, limit=limit)) except Exception: diff --git a/test_dashboard_api_limits.py b/test_dashboard_api_limits.py index 24239ae..2724c4d 100644 --- a/test_dashboard_api_limits.py +++ b/test_dashboard_api_limits.py @@ -99,6 +99,18 @@ def test_cleanup_non_integer_keep_latest_keeps_default(self): candidate_limit=25, ) + def test_history_non_integer_limit_is_400(self): + handler = _handler() + for method in ( + handler.handle_api_campaign_history, + handler.handle_api_validation_history, + handler.handle_api_preflight_history, + handler.handle_api_evidence_chain_history, + ): + handler.send_error_response.reset_mock() + method({"limit": ["nope"]}) + handler.send_error_response.assert_called_once_with(400, "limit must be an integer") + def test_options_preflight_does_not_require_a_bearer(self): old_token = dashboard_server.DASHBOARD_TOKEN old_origin = dashboard_server.DASHBOARD_CORS_ORIGIN