{"record":{"id":"233a82240de821c3","repo":"JuliusBrussee/caveman","slug":"kms-s-returned-http-d","errorCode":null,"errorMessage":"kms: %s returned HTTP %d","messagePattern":"kms: (.+?) returned HTTP (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/kms/kms.go","lineNumber":367,"sourceCode":"\t\treturn fmt.Errorf(\"kms: encode %s request: %w\", operation, err)\n\t}\n\tendpoint := c.apiBaseURL + \"/key-manager/v1alpha1/regions/\" + url.PathEscape(region) +\n\t\t\"/keys/\" + url.PathEscape(keyID) + \"/\" + operation\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"kms: create %s request: %w\", operation, err)\n\t}\n\treq.Header.Set(\"content-type\", \"application/json\")\n\treq.Header.Set(\"accept\", \"application/json\")\n\treq.Header.Set(\"x-auth-token\", c.token)\n\tresp, err := c.httpClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"kms: %s request failed: %w\", operation, err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\t_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 32<<10))\n\t\treturn fmt.Errorf(\"kms: %s returned HTTP %d\", operation, resp.StatusCode)\n\t}\n\tdata, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes+1))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"kms: read %s response: %w\", operation, err)\n\t}\n\tif len(data) > maxResponseBytes {\n\t\treturn fmt.Errorf(\"kms: %s response exceeds limit\", operation)\n\t}\n\tif err := json.Unmarshal(data, output); err != nil {\n\t\treturn fmt.Errorf(\"kms: decode %s response: %w\", operation, err)\n\t}\n\treturn nil\n}\n","sourceCodeStart":349,"sourceCodeEnd":381,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/kms/kms.go#L349-L381","documentation":"The KMS HTTP call completed but returned a status other than 200; the body (up to 32 KiB) is discarded and the status is reported. Because responses are strict, even 2xx variants other than 200 fail. The status code is the diagnostic: 401/403 credential or permission, 404 wrong region or key ID, 429 rate limiting, 5xx provider-side incident.","triggerScenarios":"Expired or revoked auth token (401); token lacking permission on this key (403); typo in region or key ID hitting a nonexistent resource (404); burst of encrypt/decrypt calls tripping rate limits (429); Scaleway incident returning 502/503.","commonSituations":"Rotated tokens not updated in deployed secrets; least-privilege policy missing kms actions; dev config pointing at a key from another project; startup probes across many instances simultaneously hitting quotas.","solutions":["Map the status: 401/403 -> fix token/permissions, 404 -> verify region+keyID, 429 -> add backoff and reduce call rate, 5xx -> check provider status page","After rotating credentials, redeploy so all instances use the new token","Confirm the key ID belongs to the same project the token can access","For 429, wrap calls with exponential backoff and respect Retry-After"],"exampleFix":"// before\n// no retry: transient 429/503 fails the operation immediately\npt, err := client.Encrypt(ctx, plaintext)\n\n// after\nvar pt []byte\nerr := retry.Do(func() error {\n    var e error\n    pt, e = client.Encrypt(ctx, plaintext)\n    return e\n}, retry.OnHTTP(429, 500, 502, 503), retry.Backoff(100*time.Millisecond))","handlingStrategy":"retry","validationCode":"null","typeGuard":null,"tryCatchPattern":"pt, err := client.Encrypt(ctx, plaintext)\nfor attempt := 0; isRetryableKMSStatus(err, 429, 500, 502, 503) && attempt < 3; attempt++ {\n\ttime.Sleep(time.Duration(1<<attempt) * 250 * time.Millisecond)\n\tpt, err = client.Encrypt(ctx, plaintext)\n}\nfunc isRetryableKMSStatus(err error, codes ...int) bool {\n\tfor _, c := range codes {\n\t\tif strings.Contains(err.Error(), fmt.Sprintf(\"HTTP %d\", c)) { return true }\n\t}\n\treturn false\n}","preventionTips":["Retry 429/5xx with exponential backoff; honor Retry-After","Alert on 401/403 immediately — they indicate credential/permission drift, never retry them","Automate token rotation so deployed secrets match the live credential"],"tags":["go","kms","http","status-code"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}