JuliusBrussee/caveman · error

awscreds: sts credential expiry

Error message

awscreds: sts credential expiry: %w

What it means

After a successful STS AssumeRoleWithWebIdentity call, the response's Credentials.Expiration timestamp is parsed with parseExpiry. If that timestamp can't be parsed into a time (unexpected format or empty), the parse error is wrapped as "awscreds: sts credential expiry" because session expiry is needed to know when to refresh the credentials.

Solutions

  1. Check the raw STS XML response's <Expiration> value; it should be RFC3339 e.g. 2026-09-20T12:00:00Z
  2. If using a non-AWS STS-compatible endpoint, fix or upgrade it to emit RFC3339 timestamps, or point at real AWS STS
  3. Add a pre-flight check against the endpoint in dev/test to catch format drift early
  4. Capture and inspect the wrapped parse error (%w) to see the exact time.Parse layout that failed

Example fix

// before
// mock STS returns <Expiration>09/20/2026 12:00PM</Expiration>
// after
// mock STS returns RFC3339:
// <Expiration>2026-09-20T12:00:00Z</Expiration>
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight a dev/test STS-compatible endpoint for RFC3339 expiration
// (integration check): issue one AssumeRoleWithWebIdentity and regex the XML
// for <Expiration>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})</Expiration>

Try / catch

creds, err := awscreds.Credentials(ctx, p)
if err != nil && strings.Contains(err.Error(), "sts credential expiry") {
    return fmt.Errorf("STS endpoint returned non-RFC3339 Expiration; use a compliant STS: %w", err)
}

Prevention

When it happens

Trigger: xml.Unmarshal succeeded but parseExpiry(c.Expiration) fails: the STS-compatible endpoint returned an Expiration string not in the expected RFC3339/ISO8601 format, an empty element, or a locally-formatted date from a non-AWS STS implementation.

Common situations: Using a third-party/minio-style STS-compatible service that formats Expiration differently; a proxy rewriting the XML; an STS API version change; hand-rolled mock STS servers in dev/test emitting wrong date formats.

Related errors


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

Appendix: source

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

	}
	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")
	}
	c := parsed.Result.Credentials
	expires, err := parseExpiry(c.Expiration)
	if err != nil {
		return nil, fmt.Errorf("awscreds: sts credential expiry: %w", err)
	}
	return &result{
		creds: awssig.Credentials{
			AccessKeyID:     strings.TrimSpace(c.AccessKeyID),
			SecretAccessKey: strings.TrimSpace(c.SecretAccessKey),
			SessionToken:    strings.TrimSpace(c.SessionToken),
		},
		expires: expires,
		source:  "web_identity",
	}, nil
}

// stsErrorCode extracts the machine-readable code of an STS ErrorResponse. The
// body itself is never returned: it can echo the web identity token.
func stsErrorCode(body []byte) string {
	var parsed struct {
		XMLName xml.Name `xml:"ErrorResponse"`
		Error   struct {

View on GitHub (pinned to 3ee70a1026)