fix: prevent panics from unsafe type assertions in trading code + add request body limit

Security & Reliability:
- Add requestBodyLimitMiddleware (1MB) to prevent OOM from oversized API payloads
- Fix defer resp.Body.Close() inside loop in getPublicIPFromAPI (connection leak)
- Add posFloat64/posString safe helpers for position map access

Panic Prevention (critical for trading):
- Convert 30+ unsafe type assertions (pos["key"].(type)) to safe comma-ok
  pattern across all exchange traders: OKX, Hyperliquid, Aster, Bybit,
  KuCoin, Gate, Bitget, Binance
- Fix auto_trader_risk.go: drawdown monitor could panic and silently stop
  monitoring, leaving positions unprotected
- Fix auto_trader_decision.go & auto_trader_loop.go: core trading loop
  position parsing now crash-proof
- All trader/ code now has zero unsafe type assertions

Frontend:
- Fix config.ts: rejected promise cached forever on network error (never retries)
This commit is contained in:
shinchan-zhai
2026-03-23 12:01:44 +08:00
parent 574e5602c1
commit 6911bc3e2b
16 changed files with 101 additions and 56 deletions

View File

@@ -36,6 +36,9 @@ func NewServer(traderManager *manager.TraderManager, st *store.Store, cryptoServ
router := gin.Default()
// Limit request body size to prevent OOM from oversized payloads (1MB default)
router.Use(requestBodyLimitMiddleware(1 << 20))
// Enable CORS
router.Use(corsMiddleware())
@@ -57,6 +60,17 @@ func NewServer(traderManager *manager.TraderManager, st *store.Store, cryptoServ
return s
}
// requestBodyLimitMiddleware limits the size of incoming request bodies.
// Returns 413 Payload Too Large if the body exceeds maxBytes.
func requestBodyLimitMiddleware(maxBytes int64) gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Body != nil {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxBytes)
}
c.Next()
}
}
// corsMiddleware CORS middleware with configurable allowed origins.
// Set CORS_ALLOWED_ORIGINS env var to a comma-separated list of origins
// (e.g. "http://localhost:5173,https://nofx.example.com").
@@ -445,25 +459,33 @@ func getPublicIPFromAPI() string {
}
for _, service := range services {
resp, err := client.Get(service)
if err != nil {
continue
}
defer resp.Body.Close()
ip := func() string {
resp, err := client.Get(service)
if err != nil {
return ""
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return ""
}
if resp.StatusCode == http.StatusOK {
body := make([]byte, 128)
n, err := resp.Body.Read(body)
if err != nil && err.Error() != "EOF" {
continue
return ""
}
ip := strings.TrimSpace(string(body[:n]))
parsedIP := net.ParseIP(ip)
candidate := strings.TrimSpace(string(body[:n]))
parsedIP := net.ParseIP(candidate)
// Verify if it's a valid IPv4 address (not containing ":")
if parsedIP != nil && parsedIP.To4() != nil {
return ip
return candidate
}
return ""
}()
if ip != "" {
return ip
}
}