diff --git a/.jules/sentinel.md b/.jules/sentinel.md index f7fa645..23bb457 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,8 @@ **Vulnerability:** The application was catching all exceptions and returning their string representation (`str(e)`) directly to the client in the HTTP 500 response. This could expose sensitive internal details (stack traces, database info, file paths). **Learning:** Developers often pass `str(e)` to `HTTPException` for convenience during debugging, but this practice frequently makes it into production code, leading to information leakage. **Prevention:** In production, catch `Exception` and raise `HTTPException` with a generic message (e.g., "Internal Server Error"). Ensure full exception details are logged using `logger.exception()` for server-side debugging. + +## 2026-03-04 - Information Leakage in Application Error Fields +**Vulnerability:** The application was catching exceptions in logic callbacks and Kafka consumers, then assigning the raw exception string to a JSON `error` field in the successful response object. This leaked internal details even when the HTTP status code was 200 OK or when processing asynchronously via Kafka. +**Learning:** Checking for HTTP 500 handlers is not enough. Review application-level error handling where business logic manually constructs error objects. +**Prevention:** Ensure that any `result["error"]` or similar fields populated in catch blocks use generic messages, while the real exception is logged server-side. diff --git a/src/regression_model_template/controller/kafka_app.py b/src/regression_model_template/controller/kafka_app.py index 0bcd83a..214730d 100644 --- a/src/regression_model_template/controller/kafka_app.py +++ b/src/regression_model_template/controller/kafka_app.py @@ -186,9 +186,9 @@ def _process_message(self, msg: Message) -> None: predictionresponse.result["error"] = error logger.error(error) prediction_result = predictionresponse.result - except Exception as e: - error = f"Error during prediction processing: {e}" - logger.exception(error) + except Exception: + error = "An error occurred during prediction processing." + logger.exception("Error during prediction processing") predictionresponse.result["error"] = error prediction_result = predictionresponse.result @@ -278,10 +278,11 @@ def my_prediction_function(input_data: PredictionRequest) -> PredictionResponse: predictionresponse.result["inference"] = outputs.to_numpy().tolist() predictionresponse.result["quality"] = 1 predictionresponse.result["error"] = None - except Exception as e: + except Exception: + logger.exception("Prediction callback failed") predictionresponse.result["inference"] = 0 predictionresponse.result["quality"] = 0 - predictionresponse.result["error"] = str(e) + predictionresponse.result["error"] = "Prediction failed." return predictionresponse # Kafka Configuration diff --git a/tests/controller/test_kafka_app_leakage.py b/tests/controller/test_kafka_app_leakage.py new file mode 100644 index 0000000..567345e --- /dev/null +++ b/tests/controller/test_kafka_app_leakage.py @@ -0,0 +1,40 @@ +from unittest.mock import MagicMock +import json +import pytest +from regression_model_template.controller.kafka_app import FastAPIKafkaService + + +def test_process_message_exception_leakage(): + # Mock dependencies + mock_producer = MagicMock() + mock_consumer = MagicMock() + + # Mock callback that raises an exception with sensitive info + def sensitive_callback(request): + raise ValueError("Sensitive Database Error: Connection failed with user 'admin'") + + service = FastAPIKafkaService( + prediction_callback=sensitive_callback, kafka_config={}, input_topic="in", output_topic="out" + ) + service.producer = mock_producer + service.consumer = mock_consumer + + # Mock a Kafka message + mock_msg = MagicMock() + mock_msg.error.return_value = None + mock_msg.value.return_value = json.dumps({"input_data": {}}).encode("utf-8") + + # Call _process_message + service._process_message(mock_msg) + + # Verify what was produced + assert mock_producer.produce.called + args, kwargs = mock_producer.produce.call_args + value = json.loads(kwargs["value"]) + + # Check if error is present + assert "error" in value + + # Check that sensitive info is NOT leaked and generic message is used + assert "Sensitive Database Error" not in value["error"] + assert value["error"] == "An error occurred during prediction processing."