diff --git a/internal/handler/agent.go b/internal/handler/agent.go index 4ef972d8b6..e947f2e6bd 100644 --- a/internal/handler/agent.go +++ b/internal/handler/agent.go @@ -435,11 +435,7 @@ func (h *AgentHandler) ListAgentVersions(c *gin.Context) { versions, err := h.agentService.ListVersions(agentID) if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": common.CodeServerError, - "data": nil, - "message": err.Error(), - }) + jsonInternalError(c, err) return } @@ -466,7 +462,7 @@ func (h *AgentHandler) ListTemplates(c *gin.Context) { templates, err := h.agentService.ListTemplates() if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } if templates == nil { diff --git a/internal/handler/datasets.go b/internal/handler/datasets.go index 426b7c1318..346aded370 100644 --- a/internal/handler/datasets.go +++ b/internal/handler/datasets.go @@ -373,7 +373,7 @@ func (h *DatasetsHandler) GetKnowledgeGraph(c *gin.Context) { indexName := fmt.Sprintf("ragflow_%s", tenantID) exists, err := docEngine.ChunkStoreExists(c.Request.Context(), indexName, datasetID) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } @@ -398,7 +398,7 @@ func (h *DatasetsHandler) GetKnowledgeGraph(c *gin.Context) { }, }) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } if searchResult == nil || len(searchResult.Chunks) == 0 { @@ -473,7 +473,7 @@ func (h *DatasetsHandler) DeleteKnowledgeGraph(c *gin.Context) { if _, err := docEngine.DeleteChunks(c.Request.Context(), map[string]interface{}{ "knowledge_graph_kwd": []string{"graph", "subgraph", "entity", "relation", "community_report"}, }, indexName, datasetID); err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } diff --git a/internal/handler/error.go b/internal/handler/error.go index 97d390ef78..2194b793e5 100644 --- a/internal/handler/error.go +++ b/internal/handler/error.go @@ -18,12 +18,24 @@ package handler import ( "net/http" + "ragflow/internal/common" "github.com/gin-gonic/gin" "go.uber.org/zap" ) +// jsonInternalError logs the original error while returning a generic message +// to avoid exposing internal implementation details in API responses. +func jsonInternalError(c *gin.Context, err error) { + common.Warn("handler internal error", + zap.Error(err), + zap.String("method", c.Request.Method), + zap.String("path", c.Request.URL.Path), + ) + jsonError(c, common.CodeServerError, common.CodeServerError.Message()) +} + // HandleNoRoute handles requests to undefined routes func HandleNoRoute(c *gin.Context) { // Python parity: GET /api/v1/auth/login/ (an empty OAuth channel) resolves diff --git a/internal/handler/error_response_test.go b/internal/handler/error_response_test.go new file mode 100644 index 0000000000..b0d4287da1 --- /dev/null +++ b/internal/handler/error_response_test.go @@ -0,0 +1,59 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package handler + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "ragflow/internal/common" + + "github.com/gin-gonic/gin" +) + +func TestJSONInternalErrorRedactsRawError(t *testing.T) { + gin.SetMode(gin.TestMode) + + recorder := httptest.NewRecorder() + c, _ := gin.CreateTestContext(recorder) + c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/files?tenant_id=secret-tenant", nil) + + jsonInternalError(c, errors.New("postgres password=secret host=10.0.0.1 table=tenant")) + + if recorder.Code != http.StatusOK { + t.Fatalf("status=%d, want %d", recorder.Code, http.StatusOK) + } + + var body map[string]interface{} + if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { + t.Fatalf("decode response body: %v", err) + } + + if got := body["message"]; got != common.CodeServerError.Message() { + t.Fatalf("message=%v, want %q", got, common.CodeServerError.Message()) + } + if got := body["code"]; got != float64(common.CodeServerError) { + t.Fatalf("code=%v, want %d", got, common.CodeServerError) + } + if strings.Contains(recorder.Body.String(), "password=secret") || strings.Contains(recorder.Body.String(), "10.0.0.1") { + t.Fatalf("response leaked raw error details: %s", recorder.Body.String()) + } +} diff --git a/internal/handler/file.go b/internal/handler/file.go index b040254547..140569e961 100644 --- a/internal/handler/file.go +++ b/internal/handler/file.go @@ -108,7 +108,7 @@ func (h *FileHandler) ListFiles(c *gin.Context) { result, err := h.fileService.ListFiles(userID, parentID, page, pageSize, orderby, desc, keywords) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } @@ -138,7 +138,7 @@ func (h *FileHandler) GetRootFolder(c *gin.Context) { // Get root folder rootFolder, err := h.fileService.GetRootFolder(userID) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } @@ -176,7 +176,7 @@ func (h *FileHandler) GetParentFolder(c *gin.Context) { // Get parent folder with permission check parentFolder, err := h.fileService.GetParentFolder(userID, fileID) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } @@ -214,7 +214,7 @@ func (h *FileHandler) GetAllParentFolders(c *gin.Context) { // Get all parent folders with permission check parentFolders, err := h.fileService.GetAllParentFolders(userID, fileID) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } @@ -251,7 +251,7 @@ func (h *FileHandler) GetFileAncestors(c *gin.Context) { // Get all parent folders with permission check parentFolders, err := h.fileService.GetAllParentFolders(userID, fileID) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } @@ -305,7 +305,7 @@ func (h *FileHandler) UploadFile(c *gin.Context) { if parentID == "" { rootFolder, err := h.fileService.GetRootFolder(userID) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } parentID = rootFolder["id"].(string) @@ -352,7 +352,7 @@ func (h *FileHandler) UploadFile(c *gin.Context) { if parentID == "" { rootFolder, err := h.fileService.GetRootFolder(userID) if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return } parentID = rootFolder["id"].(string) diff --git a/internal/handler/kb.go b/internal/handler/kb.go index 5ba5e0543a..e2997ec34b 100644 --- a/internal/handler/kb.go +++ b/internal/handler/kb.go @@ -187,7 +187,7 @@ func (h *KnowledgebaseHandler) GetDetail(c *gin.Context) { return } - jsonResponse(c, common.CodeSuccess, result, "success") + jsonResponse(c, common.CodeSuccess, result, "success") } // ListTags handles the list tags request for a knowledge base @@ -432,4 +432,4 @@ func (h *KnowledgebaseHandler) GetBasicInfo(c *gin.Context) { } jsonResponse(c, common.CodeSuccess, map[string]interface{}{}, "success") -} \ No newline at end of file +} diff --git a/internal/handler/system.go b/internal/handler/system.go index e5ac475a17..ba49d3814c 100644 --- a/internal/handler/system.go +++ b/internal/handler/system.go @@ -125,7 +125,7 @@ func (h *SystemHandler) GetStatus(c *gin.Context) { status, err := h.systemService.GetStatus() if err != nil { - jsonError(c, common.CodeServerError, err.Error()) + jsonInternalError(c, err) return }