fix: sanitize API error messages to prevent sensitive info exposure

This commit is contained in:
tinkle-community
2026-01-03 13:11:15 +08:00
parent e07dc0de86
commit 7f7c4ea2a7
5 changed files with 221 additions and 165 deletions

View File

@@ -60,7 +60,7 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
var req backtestStartRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeBadRequest(c, "Invalid request parameters")
return
}
@@ -78,16 +78,16 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
if cfg.StrategyID != "" {
strategy, err := s.store.Strategy().Get(cfg.UserID, cfg.StrategyID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to load strategy: %v", err)})
SafeBadRequest(c, "Failed to load strategy")
return
}
if strategy == nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("strategy not found: %s", cfg.StrategyID)})
SafeBadRequest(c, "Strategy not found")
return
}
var strategyConfig store.StrategyConfig
if err := json.Unmarshal([]byte(strategy.Config), &strategyConfig); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse strategy config: %v", err)})
SafeBadRequest(c, "Failed to parse strategy config")
return
}
cfg.SetLoadedStrategy(&strategyConfig)
@@ -102,7 +102,7 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
if len(cfg.Symbols) == 0 {
symbols, err := s.resolveStrategyCoins(&strategyConfig)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to resolve coins from strategy: %v", err)})
SafeBadRequest(c, "Failed to resolve coins from strategy")
return
}
cfg.Symbols = symbols
@@ -111,7 +111,7 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
}
if err := s.hydrateBacktestAIConfig(&cfg); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeBadRequest(c, "Failed to configure AI model")
return
}
@@ -120,7 +120,7 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
runner, err := s.backtestManager.Start(context.Background(), cfg)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeError(c, http.StatusBadRequest, "Failed to start backtest", err)
return
}
@@ -149,11 +149,11 @@ func (s *Server) handleBacktestControl(c *gin.Context, fn func(string) error) {
var req runIDRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeBadRequest(c, "Invalid request parameters")
return
}
if req.RunID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "run_id is required"})
SafeBadRequest(c, "run_id is required")
return
}
@@ -162,7 +162,7 @@ func (s *Server) handleBacktestControl(c *gin.Context, fn func(string) error) {
}
if err := fn(req.RunID); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeError(c, http.StatusBadRequest, "Failed to execute backtest operation", err)
return
}
@@ -181,11 +181,11 @@ func (s *Server) handleBacktestLabel(c *gin.Context) {
}
var req labelRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeBadRequest(c, "Invalid request parameters")
return
}
if strings.TrimSpace(req.RunID) == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "run_id is required"})
SafeBadRequest(c, "run_id is required")
return
}
userID := normalizeUserID(c.GetString("user_id"))
@@ -194,7 +194,7 @@ func (s *Server) handleBacktestLabel(c *gin.Context) {
}
meta, err := s.backtestManager.UpdateLabel(req.RunID, req.Label)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
SafeInternalError(c, "Update backtest label", err)
return
}
c.JSON(http.StatusOK, meta)
@@ -207,11 +207,11 @@ func (s *Server) handleBacktestDelete(c *gin.Context) {
}
var req runIDRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeBadRequest(c, "Invalid request parameters")
return
}
if strings.TrimSpace(req.RunID) == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "run_id is required"})
SafeBadRequest(c, "run_id is required")
return
}
userID := normalizeUserID(c.GetString("user_id"))
@@ -219,7 +219,7 @@ func (s *Server) handleBacktestDelete(c *gin.Context) {
return
}
if err := s.backtestManager.Delete(req.RunID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
SafeInternalError(c, "Delete backtest run", err)
return
}
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
@@ -277,7 +277,7 @@ func (s *Server) handleBacktestRuns(c *gin.Context) {
metas, err := s.backtestManager.ListRuns()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
SafeInternalError(c, "List backtest runs", err)
return
}
stateFilter := strings.ToLower(strings.TrimSpace(c.Query("state")))
@@ -349,7 +349,7 @@ func (s *Server) handleBacktestEquity(c *gin.Context) {
points, err := s.backtestManager.LoadEquity(runID, timeframe, limit)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeError(c, http.StatusBadRequest, "Failed to load equity data", err)
return
}
c.JSON(http.StatusOK, points)
@@ -375,7 +375,7 @@ func (s *Server) handleBacktestTrades(c *gin.Context) {
events, err := s.backtestManager.LoadTrades(runID, limit)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeError(c, http.StatusBadRequest, "Failed to load trades", err)
return
}
c.JSON(http.StatusOK, events)
@@ -404,7 +404,7 @@ func (s *Server) handleBacktestMetrics(c *gin.Context) {
c.JSON(http.StatusAccepted, gin.H{"error": "metrics not ready yet"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeError(c, http.StatusBadRequest, "Failed to load metrics", err)
return
}
c.JSON(http.StatusOK, metrics)
@@ -427,7 +427,7 @@ func (s *Server) handleBacktestTrace(c *gin.Context) {
cycle := queryInt(c, "cycle", 0)
record, err := s.backtestManager.GetTrace(runID, cycle)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
SafeNotFound(c, "Trace record")
return
}
c.JSON(http.StatusOK, record)
@@ -461,7 +461,7 @@ func (s *Server) handleBacktestDecisions(c *gin.Context) {
records, err := backtest.LoadDecisionRecords(runID, limit, offset)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
SafeInternalError(c, "Load decision records", err)
return
}
c.JSON(http.StatusOK, records)
@@ -483,7 +483,7 @@ func (s *Server) handleBacktestExport(c *gin.Context) {
}
path, err := s.backtestManager.ExportRun(runID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
SafeError(c, http.StatusBadRequest, "Failed to export backtest", err)
return
}
defer os.Remove(path)
@@ -536,8 +536,7 @@ func (s *Server) handleBacktestKlines(c *gin.Context) {
klines, err := market.GetKlinesRange(symbol, timeframe, startTime, endTime)
if err != nil {
logger.Errorf("Failed to fetch klines for %s: %v", symbol, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to fetch klines: %v", err)})
SafeInternalError(c, "Fetch klines", err)
return
}
@@ -620,11 +619,11 @@ func writeBacktestAccessError(c *gin.Context, err error) bool {
}
switch {
case errors.Is(err, errBacktestForbidden):
c.JSON(http.StatusForbidden, gin.H{"error": "No permission to access this backtest task"})
SafeForbidden(c, "No permission to access this backtest task")
case errors.Is(err, os.ErrNotExist), errors.Is(err, sql.ErrNoRows):
c.JSON(http.StatusNotFound, gin.H{"error": "Backtest task does not exist"})
SafeNotFound(c, "Backtest task")
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
SafeInternalError(c, "Access backtest", err)
}
return true
}