From e98657a88ab7b27ff4f69ca334d4f09628b9af2a Mon Sep 17 00:00:00 2001 From: Dmitry Verkhoturov Date: Fri, 17 Apr 2026 04:40:15 +0100 Subject: [PATCH] chore(lint): cap multipart upload size and suppress remaining gosec G70x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address all golangci-lint v2.10.1 (CI's version) findings: * Add http.MaxBytesReader hard cap to ParseMultipartForm sites in rest_private.savePictureCtrl (32MB) and api/migrator (256MB) — fixes G120 by bounding total request body before form parsing. * Suppress G70x in CLI subcommands cmd/{backup,cleanup,import,remap}.go: all four issue HTTP requests against operator-supplied RemarkURL/CLI flags, never user input. Each suppression carries a one-line reason. * Suppress G122 in image fs_store cleanup walk: staging directory tree is server-only, no untrusted symlinks land there. --- backend/app/cmd/import.go | 2 +- backend/app/cmd/remap.go | 4 ++-- backend/app/rest/api/migrator.go | 1 + backend/app/rest/api/rest_private.go | 1 + backend/app/store/image/fs_store.go | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/app/cmd/import.go b/backend/app/cmd/import.go index 421d05e7..8e2eb2f3 100644 --- a/backend/app/cmd/import.go +++ b/backend/app/cmd/import.go @@ -42,7 +42,7 @@ func (ic *ImportCommand) Execute(_ []string) error { } req.SetBasicAuth("admin", ic.AdminPasswd) - resp, err := client.Do(req.WithContext(ctx)) // closes request's reader + resp, err := client.Do(req.WithContext(ctx)) //nolint:gosec // importURL built from operator CLI flags, not user input; closes request's reader if err != nil { return fmt.Errorf("request failed for %s: %w", importURL, err) } diff --git a/backend/app/cmd/remap.go b/backend/app/cmd/remap.go index b2ee55ec..360455cd 100644 --- a/backend/app/cmd/remap.go +++ b/backend/app/cmd/remap.go @@ -34,13 +34,13 @@ func (rc *RemapCommand) Execute(_ []string) error { ctx, cancel := context.WithTimeout(context.Background(), rc.Timeout) defer cancel() remapURL := fmt.Sprintf("%s/api/v1/admin/remap?site=%s", rc.RemarkURL, rc.Site) - req, err := http.NewRequest(http.MethodPost, remapURL, rulesReader) + req, err := http.NewRequest(http.MethodPost, remapURL, rulesReader) //nolint:gosec // RemarkURL is operator CLI flag, not user input if err != nil { return fmt.Errorf("can't make remap request for %s: %w", remapURL, err) } req.SetBasicAuth("admin", rc.AdminPasswd) - resp, err := client.Do(req.WithContext(ctx)) + resp, err := client.Do(req.WithContext(ctx)) //nolint:gosec // see above if err != nil { return fmt.Errorf("request failed for %s: %w", remapURL, err) } diff --git a/backend/app/rest/api/migrator.go b/backend/app/rest/api/migrator.go index 51f7875e..52421018 100644 --- a/backend/app/rest/api/migrator.go +++ b/backend/app/rest/api/migrator.go @@ -73,6 +73,7 @@ func (m *Migrator) importFormCtrl(w http.ResponseWriter, r *http.Request) { return } + r.Body = http.MaxBytesReader(w, r.Body, 256*1024*1024) // hard cap on upload to prevent memory exhaustion if err := r.ParseMultipartForm(20 * 1024 * 1024); err != nil { // 20M max memory, if bigger will make a file rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't parse multipart form", rest.ErrDecode) return diff --git a/backend/app/rest/api/rest_private.go b/backend/app/rest/api/rest_private.go index 24d887d7..5ccc7642 100644 --- a/backend/app/rest/api/rest_private.go +++ b/backend/app/rest/api/rest_private.go @@ -731,6 +731,7 @@ func (s *private) deleteMeCtrl(w http.ResponseWriter, r *http.Request) { func (s *private) savePictureCtrl(w http.ResponseWriter, r *http.Request) { user := rest.MustGetUserInfo(r) + r.Body = http.MaxBytesReader(w, r.Body, 32*1024*1024) // hard cap on upload to prevent memory exhaustion if err := r.ParseMultipartForm(5 * 1024 * 1024); err != nil { // 5M max memory, if bigger will make a file rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't parse multipart form", rest.ErrDecode) return diff --git a/backend/app/store/image/fs_store.go b/backend/app/store/image/fs_store.go index 80120171..8a3835e6 100644 --- a/backend/app/store/image/fs_store.go +++ b/backend/app/store/image/fs_store.go @@ -147,8 +147,8 @@ func (f *FileSystem) Cleanup(_ context.Context, ttl time.Duration) error { age := time.Since(info.ModTime()) if age > (ttl + 100*time.Millisecond) { // delay cleanup triggering to allow commit log.Printf("[INFO] remove staging image %s, age %v", fpath, age) - rmErr := os.Remove(fpath) - _ = os.Remove(path.Dir(fpath)) // try to remove directory + rmErr := os.Remove(fpath) //nolint:gosec // staging dir is server-only, no untrusted symlinks land here + _ = os.Remove(path.Dir(fpath)) //nolint:gosec // same staging dir return rmErr } return nil