JuliusBrussee/caveman · error
bedrock: missing AWS credentials in x-cave-upstream-key
Error message
bedrock: missing AWS credentials in x-cave-upstream-key
What it means
parseAWSCredentials decodes the accessKeyId:secretAccessKey[:sessionToken] form and fails closed on an empty input: an empty x-cave-upstream-key can never produce valid SigV4 credentials, so it is rejected instead of silently sending an unsigned request.
Solutions
- Provide the full accessKeyId:secretAccessKey[:sessionToken] string in x-cave-upstream-key
- Check the env var / secret store actually contains the AWS keys and that the correct variable name is wired to the Bedrock provider
- If you meant to use a Bedrock API key instead of IAM, ensure the key is non-empty (it will then be inferred as bedrock_api_key)
Example fix
// before // x-cave-upstream-key: (empty) // after // x-cave-upstream-key: AKIAIOSFODNN7EXAMPLE:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Defensive patterns
Strategy: validation
Validate before calling
raw := strings.TrimSpace(cfg.BedrockUpstreamKey)
if raw == "" { return errors.New("x-cave-upstream-key is required for Bedrock IAM auth") } Prevention
- Check required credentials at provider startup, not per-request
- Wire secret-store values with tests asserting non-empty
- Trim env values before assignment
When it happens
Trigger: parseAWSCredentials is called with raw == "" — either directly from SanitizeAndMapHeaders when building SigV4 credentials, or from credentialAuthKind's inference path where the AuthKind check itself calls parseAWSCredentials on an empty key.
Common situations: AWS access keys env var or x-cave-upstream-key header unset/empty; secret store returning an empty string for a missing secret; whitespace value that was trimmed to empty before the call.
Related errors
- ErrSigV4Configuration
- AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set…
- awssig: incomplete AWS credentials
- bedrock: malformed AWS credentials (want…
- bedrock: request signing failed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/544bdd7b4e657449.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/providers/bedrock/signing.go:226
// bearer. Every other opaque value is a Bedrock API key.
if _, err := parseAWSCredentials(key); err == nil {
return "aws_access_keys", nil
}
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)