From 057261283342fcd33a758cdae0383a7e73099004 Mon Sep 17 00:00:00 2001 From: soldosluka857 Date: Sat, 30 May 2026 02:22:27 +0000 Subject: [PATCH] fix: add field whitelist to site update, fix HTTP status codes in site handlers - SiteService.Update: whitelist updatable fields to prevent injection of id, created_at, deleted_at, login_status, upload_bytes, download_bytes - createSiteHandler: return 201 Created (was 200 OK) - siteSearchHandler: return 500 for infra errors (was 400) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- internal/handler/sites.go | 4 ++-- internal/service/site.go | 47 +++++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/internal/handler/sites.go b/internal/handler/sites.go index 1aad28e..c22c262 100644 --- a/internal/handler/sites.go +++ b/internal/handler/sites.go @@ -102,7 +102,7 @@ func createSiteHandler(svc *service.Container) gin.HandlerFunc { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, site) + c.JSON(http.StatusCreated, site) } } @@ -156,7 +156,7 @@ func siteSearchHandler(svc *service.Container) gin.HandlerFunc { } results, err := svc.Site.Search(c.Request.Context(), keyword) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"items": results, "total": len(results)}) diff --git a/internal/service/site.go b/internal/service/site.go index aa87e52..4065831 100644 --- a/internal/service/site.go +++ b/internal/service/site.go @@ -213,20 +213,53 @@ func (s *SiteService) FindByID(ctx context.Context, id string) (*model.Site, err return &site, err } +// siteUpdatableFields is the whitelist of columns that may be patched via +// the update endpoint. Fields like id, created_at, deleted_at, login_status, +// upload_bytes, download_bytes are excluded to prevent injection. +var siteUpdatableFields = map[string]bool{ + "name": true, + "url": true, + "type": true, + "auth_type": true, + "api_key": true, + "cookie": true, + "auth_header": true, + "user_agent": true, + "rss_url": true, + "timeout": true, + "priority": true, + "use_proxy": true, + "rate_limit": true, + "browser_emulation": true, + "downloader": true, + "enabled": true, + "is_default": true, + "extra": true, +} + // Update applies a partial patch to an existing site. func (s *SiteService) Update(ctx context.Context, id string, updates map[string]any) error { if id == "" { return errors.New("site id required") } - if raw, ok := updates["url"].(string); ok { - updates["url"] = strings.TrimRight(strings.TrimSpace(raw), "/") - } - for _, key := range []string{"api_key", "cookie", "auth_header"} { - if raw, ok := updates[key].(string); ok && strings.TrimSpace(raw) == "" { - delete(updates, key) + filtered := make(map[string]any, len(updates)) + for k, v := range updates { + if siteUpdatableFields[k] { + filtered[k] = v } } - return s.repo.DB.WithContext(ctx).Model(&model.Site{}).Where("id = ?", id).Updates(updates).Error + if len(filtered) == 0 { + return errors.New("no valid fields to update") + } + if raw, ok := filtered["url"].(string); ok { + filtered["url"] = strings.TrimRight(strings.TrimSpace(raw), "/") + } + for _, key := range []string{"api_key", "cookie", "auth_header"} { + if raw, ok := filtered[key].(string); ok && strings.TrimSpace(raw) == "" { + delete(filtered, key) + } + } + return s.repo.DB.WithContext(ctx).Model(&model.Site{}).Where("id = ?", id).Updates(filtered).Error } // Delete removes a site.