fix: 修复 minimax_service 安全隐患和数据竞争

- 添加 HTTP 响应状态码检查,防止状态错误被忽略
- 添加 HTTP Client 结构体复用,避免每次创建新客户端
- 在 compressImageIfNeeded 中使用带 Timeout 的 Client
- 添加 URL scheme 校验,防止 SSRF 攻击
- 使用 LimitedReader 限制下载图片大小(10MB)
- 修复 GetJob 数据竞争,复制数据后返回

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zerosaturation 2026-04-07 23:59:00 +08:00
parent d09f1122ce
commit f43b06e13d

View File

@ -61,6 +61,7 @@ type minimaxService struct {
config *config.AssetConfig
jobs map[string]*ImageGenerationJob
jobsLock sync.RWMutex
client *http.Client
}
// NewMinimaxService 创建 MiniMax 服务
@ -68,6 +69,7 @@ func NewMinimaxService(cfg *config.AssetConfig) MinimaxService {
svc := &minimaxService{
config: cfg,
jobs: make(map[string]*ImageGenerationJob),
client: &http.Client{Timeout: 120 * time.Second},
}
go svc.cleanupExpiredJobs()
return svc
@ -111,7 +113,11 @@ func (s *minimaxService) GetJob(ctx context.Context, jobID string, userID, starI
return nil, fmt.Errorf("access denied")
}
return job, nil
// Copy data to avoid race
result := *job
result.Images = make([]string, len(job.Images))
copy(result.Images, job.Images)
return &result, nil
}
// processJob 异步处理任务
@ -192,7 +198,6 @@ func (s *minimaxService) callMiniMaxAPI(model, prompt, aspectRatio string, refs
return nil, err
}
client := &http.Client{Timeout: 120 * time.Second}
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
@ -200,12 +205,17 @@ func (s *minimaxService) callMiniMaxAPI(model, prompt, aspectRatio string, refs
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
resp, err := s.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
}
var result struct {
Images []struct {
URL string `json:"url"`
@ -224,13 +234,14 @@ func (s *minimaxService) callMiniMaxAPI(model, prompt, aspectRatio string, refs
// compressImageIfNeeded 下载并压缩图片
func (s *minimaxService) compressImageIfNeeded(imageURL string) (string, error) {
resp, err := http.Get(imageURL)
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Get(imageURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
imgData, err := io.ReadAll(resp.Body)
imgData, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024)) // 10MB limit
if err != nil {
return "", err
}
@ -295,6 +306,9 @@ func validateURL(rawURL string) error {
if err != nil {
return err
}
if u.Scheme != "http" && u.Scheme != "https" {
return fmt.Errorf("unsupported scheme: %s", u.Scheme)
}
host := u.Hostname()
ip := net.ParseIP(host)