From d40a1a6de90751f0208cd9310793a5f56c19b2fa Mon Sep 17 00:00:00 2001 From: Prem Kumar Kalle Date: Thu, 6 Aug 2026 15:05:21 -0700 Subject: [PATCH] Fix(curl): surface auth errors and avoid panic on nil HTTP response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MakeCurlRequest assumed that a non-nil error always came with an HTTP response (e.g. a 4xx/5xx from the API). That assumption breaks when the request fails before reaching the API — for example a token refresh / client-credentials authentication failure in the UAA request wrapper, which returns an error with a nil *http.Response. In that case: - Without --fail, the error was swallowed and cf curl printed empty output, giving the user no indication anything went wrong. - With --fail, the code dereferenced httpResponse.StatusCode on the nil response and panicked. Only surface CurlExit22Error when an HTTP response is actually present; when the response is nil, return the underlying error directly so the user sees a real message (e.g. "Bad credentials") and cf curl exits cleanly instead of panicking. Existing behavior for real HTTP error responses (print body by default, fail only with --fail) is unchanged. Signed-off-by: Prem Kumar Kalle --- actor/v7action/curl.go | 15 +++++++++++++-- actor/v7action/curl_test.go | 28 ++++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 20 ++++++++++---------- 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/actor/v7action/curl.go b/actor/v7action/curl.go index 25bb11d3c84..d987962a266 100644 --- a/actor/v7action/curl.go +++ b/actor/v7action/curl.go @@ -47,8 +47,19 @@ func (actor Actor) MakeCurlRequest( requestBodyBytes, ) - if err != nil && failOnHTTPError { - return nil, nil, translatableerror.CurlExit22Error{StatusCode: httpResponse.StatusCode} + if err != nil { + // A nil HTTP response means the request never reached the API (e.g. a token + // refresh or other authentication failure in the request wrapper). There is no + // status code to read and no response body to print, so surface the error + // directly. This also avoids a nil-pointer dereference on httpResponse below + // when the fail-on-http-error flag is set. + if httpResponse == nil { + return nil, nil, err + } + + if failOnHTTPError { + return nil, nil, translatableerror.CurlExit22Error{StatusCode: httpResponse.StatusCode} + } } return responseBody, httpResponse, nil diff --git a/actor/v7action/curl_test.go b/actor/v7action/curl_test.go index bb3d8d8628e..21c72be0fcc 100644 --- a/actor/v7action/curl_test.go +++ b/actor/v7action/curl_test.go @@ -247,5 +247,33 @@ var _ = Describe("Curl Actions", func() { }) }) }) + + When("the request fails before an HTTP response is received", func() { + // e.g. a token refresh / authentication failure in the request wrapper, where + // no request reaches the API and there is no HTTP response. + BeforeEach(func() { + mockErr = errors.New("Bad credentials") + mockResponseBody = nil + mockHTTPResponse = nil + }) + + It("surfaces the error instead of returning empty output", func() { + Expect(executeErr).To(MatchError("Bad credentials")) + Expect(responseBody).To(BeNil()) + Expect(httpResponse).To(BeNil()) + }) + + When("the fail-on-http-errors flag is set", func() { + BeforeEach(func() { + failOnHTTPError = true + }) + + It("surfaces the error without panicking on the nil response", func() { + Expect(executeErr).To(MatchError("Bad credentials")) + Expect(responseBody).To(BeNil()) + Expect(httpResponse).To(BeNil()) + }) + }) + }) }) }) diff --git a/go.mod b/go.mod index 30e82c122ee..6117f917158 100644 --- a/go.mod +++ b/go.mod @@ -77,6 +77,8 @@ require ( github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/otel v1.45.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/mod v0.38.0 // indirect diff --git a/go.sum b/go.sum index 4e8765df2a1..e7de3c042e8 100644 --- a/go.sum +++ b/go.sum @@ -277,16 +277,16 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.step.sm/crypto v0.85.0 h1:h3U9gzCcxP1fWbseqy+CwsTxzHFNuoblefy7YP4jBHk= go.step.sm/crypto v0.85.0/go.mod h1:Rp/BdP0/ZJ7eYREhXcE4izCABLp0e8ziFqb0LBNG7Cc= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=