JuliusBrussee/caveman · error

bedrock: malformed AWS credentials (want…

Error message

bedrock: malformed AWS credentials (want accessKeyId:secretAccessKey[:sessionToken])

What it means

The credential string is expected as accessKeyId:secretAccessKey[:sessionToken]. If splitting on ":" (max 3 parts) yields fewer than 2 parts, or the access key or secret part is empty, the value is malformed and rejected so no partial SigV4 credentials are ever used.

Solutions

  1. Set the value to the complete accessKeyId:secretAccessKey form (append :sessionToken if using temporary credentials)
  2. Verify no intermediate config step split or trimmed the colon-delimited value
  3. If you only have a Bedrock API key (not IAM keys), do not use it here — set it as a plain key so it is inferred as bedrock_api_key

Example fix

// before
// x-cave-upstream-key: AKIAIOSFODNN7EXAMPLE
// after
// x-cave-upstream-key: AKIAIOSFODNN7EXAMPLE:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Defensive patterns

Strategy: validation

Validate before calling

func validAWSKeyForm(raw string) bool {
	parts := strings.SplitN(raw, ":", 3)
	return len(parts) >= 2 && parts[0] != "" && parts[1] != ""
}
if !validAWSKeyForm(raw) { return errors.New("x-cave-upstream-key must be accessKeyId:secretAccessKey[:sessionToken]") }

Prevention

When it happens

Trigger: parseAWSCredentials receives a non-empty string that is not colon-form: a lone access key ("AKIA..."), only a secret, ":secret" with empty key part, "key:" with empty secret part, or a key pasted with the colon omitted.

Common situations: Storing only the AWS access key ID in x-cave-upstream-key; copying the key pair but dropping the colon or pasting only one half; a session-token-only value; config tools stripping the colon or splitting the value across fields.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at proxy/providers/bedrock/signing.go:230

		if strings.HasPrefix(key, "AKIA") || strings.HasPrefix(key, "ASIA") {
			return "aws_access_keys", nil
		}
		return "bedrock_api_key", nil
	default:
		return "", fmt.Errorf("bedrock: unsupported credential auth kind")
	}
}

// parseAWSCredentials decodes the "accessKeyId:secretAccessKey[:sessionToken]"
// form carried in x-cave-upstream-key into awssig.Credentials. It fails closed:
// a missing access key or secret is an error, never an unsigned passthrough.
func parseAWSCredentials(raw string) (awssig.Credentials, error) {
	if raw == "" {
		return awssig.Credentials{}, fmt.Errorf("bedrock: missing AWS credentials in x-cave-upstream-key")
	}
	parts := strings.SplitN(raw, ":", 3)
	if len(parts) < 2 || parts[0] == "" || parts[1] == "" {
		return awssig.Credentials{}, fmt.Errorf("bedrock: malformed AWS credentials (want accessKeyId:secretAccessKey[:sessionToken])")
	}
	creds := awssig.Credentials{AccessKeyID: parts[0], SecretAccessKey: parts[1]}
	if len(parts) == 3 {
		creds.SessionToken = parts[2]
	}
	return creds, nil
}

// copyIfPresent copies a header from src to dst when present (case-insensitive).
func copyIfPresent(dst, src http.Header, name string) {
	if values := src.Values(name); len(values) > 0 {
		for _, v := range values {
			dst.Add(name, v)
		}
	}
}

View on GitHub (pinned to 3ee70a1026)