diff --git a/application/api/user/sources/routes.py b/application/api/user/sources/routes.py index d53fb0a7..3f7d447a 100644 --- a/application/api/user/sources/routes.py +++ b/application/api/user/sources/routes.py @@ -5,6 +5,7 @@ import math from flask import current_app, jsonify, make_response, redirect, request from flask_restx import fields, Namespace, Resource +from pydantic import ValidationError from application.api import api from application.api.user.tasks import reingest_source_task, sync_source @@ -573,11 +574,20 @@ class SourceConfigResource(Resource): jsonify({"success": False, "message": "Invalid config body"}), 400 ) # Strict validation (D7 strict-on-write): reject invalid config → 400. + # Return a static message (no exception text) so validation internals + # aren't exposed to the caller; the client validates the same rules + # before submitting. try: new_config = SourceConfig.model_validate(body) - except Exception as err: + except ValidationError: return make_response( - jsonify({"success": False, "message": f"Invalid config: {err}"}), 400 + jsonify( + { + "success": False, + "message": "Invalid config: one or more fields failed validation.", + } + ), + 400, ) try: diff --git a/application/retriever/dispatcher.py b/application/retriever/dispatcher.py index 0efdd742..f995aaa1 100644 --- a/application/retriever/dispatcher.py +++ b/application/retriever/dispatcher.py @@ -287,7 +287,14 @@ class Dispatcher(BaseRetriever): try: group_docs = retriever.search(query) if query else retriever.search() except Exception as exc: - logger.error("Group '%s' search failed: %s", group["retriever"], exc) + # Log the exception type only — a raw exception message can + # carry the vector-store DSN (with credentials) on connection + # errors. + logger.error( + "Group '%s' search failed: %s", + group["retriever"], + type(exc).__name__, + ) continue context = {"query": query, "retriever": group["retriever"]} group_docs = self._run_stages(