JuliusBrussee/caveman · error

awscreds: read sts response

Error message

awscreds: read sts response: %w

What it means

The STS AssumeRoleWithWebIdentity response body is read with io.ReadAll(io.LimitReader(resp.Body, maxBody)). If reading the body fails mid-stream — connection reset, unexpected EOF, context canceled while streaming — the error is wrapped as "awscreds: read sts response" and the credential fetch fails.

Solutions

  1. Retry the credential fetch with backoff — body-read resets are usually transient
  2. Extend the context deadline if ctx cancellation is the cause
  3. Check for proxies/LBs between the client and STS that truncate responses; test with curl against the same endpoint
  4. Ensure the STS endpoint is the official one (or a healthy compatible endpoint) rather than a flaky mirror

Example fix

// before
creds, err := awscreds.Credentials(ctx, p) // unexpected EOF during body read
// after
var creds *awscreds.Result
for i := 0; i < 3; i++ {
    creds, err = awscreds.Credentials(ctx, p)
    if err == nil || !strings.Contains(err.Error(), "read sts response") { break }
    time.Sleep(time.Duration(1<<i) * 100 * time.Millisecond)
}
Defensive patterns

Strategy: retry

Validate before calling

// no caller-side pre-check can prevent a mid-body reset; instead preflight the endpoint
resp, err := http.Head(stsEndpoint)
if err == nil { resp.Body.Close() } // ensures the path is at least reachable

Type guard

func isBodyReadFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "read sts response")
}

Try / catch

creds, err := awscreds.Credentials(ctx, p)
if isBodyReadFailure(err) {
    // transient stream break: one retry is usually enough
    time.Sleep(250 * time.Millisecond)
    creds, err = awscreds.Credentials(ctx, p)
}

Prevention

When it happens

Trigger: io.ReadAll inside fromWebIdentity returns err after p.sts.Do succeeded: server closed the connection mid-body, TLS truncation, proxy dropped the stream, or ctx was canceled during the read.

Common situations: Flaky networks/NAT dropping long-lived connections; load balancers in front of a self-hosted STS terminating connections; aggressive context timeouts cutting the read short; intermediate proxies mangling the response.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/380f2e07cc8cf81d. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/awscreds/awscreds.go:344

		"RoleSessionName":  {sessionName},
		"WebIdentityToken": {token},
		"DurationSeconds":  {"3600"},
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.stsEndpoint, strings.NewReader(form.Encode()))
	if err != nil {
		return nil, fmt.Errorf("awscreds: build sts request: %w", err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("Accept", "application/xml")
	resp, err := p.sts.Do(req)
	if err != nil {
		// A transport error can carry the request URL but never the form body.
		return nil, fmt.Errorf("awscreds: sts assume role with web identity failed: %w", err)
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(io.LimitReader(resp.Body, maxBody))
	if err != nil {
		return nil, fmt.Errorf("awscreds: read sts response: %w", err)
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("awscreds: sts assume role with web identity: http %d%s", resp.StatusCode, stsErrorCode(body))
	}
	var parsed struct {
		XMLName xml.Name `xml:"AssumeRoleWithWebIdentityResponse"`
		Result  struct {
			Credentials struct {
				AccessKeyID     string `xml:"AccessKeyId"`
				SecretAccessKey string `xml:"SecretAccessKey"`
				SessionToken    string `xml:"SessionToken"`
				Expiration      string `xml:"Expiration"`
			} `xml:"Credentials"`
		} `xml:"AssumeRoleWithWebIdentityResult"`
	}
	if err := xml.Unmarshal(body, &parsed); err != nil {
		return nil, errors.New("awscreds: sts returned an unparseable response")
	}

View on GitHub (pinned to 3ee70a1026)