55 "context"
66 "encoding/json"
77 "fmt"
8- "io"
98 "net/http"
109 "net/url"
1110 "path"
@@ -22,6 +21,12 @@ import (
2221 "github.com/anchore/quill/internal/log"
2322 "github.com/anchore/quill/internal/redact"
2423 "github.com/anchore/quill/internal/urlvalidate"
24+ "github.com/anchore/quill/internal/utils"
25+ )
26+
27+ const (
28+ maxAPIResponseSize = 5 * 1024 * 1024 // 5 MB for API JSON responses
29+ maxLogResponseSize = 50 * 1024 * 1024 // 50 MB for log files
2530)
2631
2732type api interface {
@@ -63,7 +68,7 @@ func (s APIClient) submissionRequest(ctx context.Context, request submissionRequ
6368 return nil , err
6469 }
6570
66- response , err := s .http .post (ctx , s .api , bytes .NewReader (requestBytes ))
71+ response , err := s .http .post (ctx , s .api , bytes .NewReader (requestBytes )) //nolint:bodyclose // body is closed in handleResponse
6772 body , err := s .handleResponse (response , err )
6873 if err != nil {
6974 return nil , err
@@ -119,7 +124,7 @@ func (s APIClient) uploadBinary(ctx context.Context, response submissionResponse
119124}
120125
121126func (s APIClient ) submissionStatusRequest (ctx context.Context , id string ) (* submissionStatusResponse , error ) {
122- response , err := s .http .get (ctx , joinURL (s .api , id ), nil )
127+ response , err := s .http .get (ctx , joinURL (s .api , id ), nil ) //nolint:bodyclose // body is closed in handleResponse
123128 body , err := s .handleResponse (response , err )
124129 if err != nil {
125130 return nil , err
@@ -133,7 +138,7 @@ func (s APIClient) submissionStatusRequest(ctx context.Context, id string) (*sub
133138}
134139
135140func (s APIClient ) submissionList (ctx context.Context ) (* submissionListResponse , error ) {
136- response , err := s .http .get (ctx , s .api , nil )
141+ response , err := s .http .get (ctx , s .api , nil ) //nolint:bodyclose // body is closed in handleResponse
137142 body , err := s .handleResponse (response , err )
138143 if err != nil {
139144 return nil , err
@@ -147,7 +152,7 @@ func (s APIClient) submissionList(ctx context.Context) (*submissionListResponse,
147152}
148153
149154func (s APIClient ) submissionLogs (ctx context.Context , id string ) (string , error ) {
150- metadataResp , err := s .http .get (ctx , joinURL (s .api , id , "logs" ), nil )
155+ metadataResp , err := s .http .get (ctx , joinURL (s .api , id , "logs" ), nil ) //nolint:bodyclose // body is closed in handleResponse
151156 body , err := s .handleResponse (metadataResp , err )
152157 if err != nil {
153158 return "" , fmt .Errorf ("unable to fetch log metadata with ID=%s: %w" , id , err )
@@ -160,9 +165,10 @@ func (s APIClient) submissionLogs(ctx context.Context, id string) (string, error
160165
161166 redactPresignedURLParams (resp .Data .Attributes .DeveloperLogURL )
162167
163- // fetch logs without auth header (it's a presigned URL with its own auth)
168+ // fetch logs without auth (presigned URL), with redirect validation for SSRF protection.
169+ // use a larger size limit since log files can be bigger than typical API responses.
164170 logsResp , err := s .http .getUnauthenticated (ctx , resp .Data .Attributes .DeveloperLogURL )
165- contents , err := s .handleResponse (logsResp , err )
171+ contents , err := s .handleResponseWithLimit (logsResp , err , maxLogResponseSize )
166172 if err != nil {
167173 return "" , fmt .Errorf ("unable to fetch log destination with ID=%s: %w" , id , err )
168174 }
@@ -171,16 +177,28 @@ func (s APIClient) submissionLogs(ctx context.Context, id string) (string, error
171177}
172178
173179func (s APIClient ) handleResponse (response * http.Response , err error ) ([]byte , error ) {
180+ return s .handleResponseWithLimit (response , err , maxAPIResponseSize )
181+ }
182+
183+ func (s APIClient ) handleResponseWithLimit (response * http.Response , err error , maxBytes int64 ) ([]byte , error ) {
184+ // ensure body is always closed, even if there's an error
185+ if response != nil && response .Body != nil {
186+ defer response .Body .Close ()
187+ }
188+
174189 if err != nil {
175190 return nil , err
176191 }
177192
193+ if response == nil {
194+ return nil , fmt .Errorf ("nil response" )
195+ }
196+
178197 var body []byte
179198
180199 if response .Body != nil {
181- defer response .Body .Close ()
182-
183- body , err = io .ReadAll (response .Body )
200+ // limit response size to prevent memory exhaustion from malicious responses
201+ body , err = utils .ReadAllLimited (response .Body , maxBytes )
184202 if err != nil {
185203 return nil , err
186204 }
0 commit comments