{"record":{"id":"3effbaa47626beb8","repo":"benbjohnson/litestream","slug":"s3-sse-customer-key-requires-https-endpoint-http","errorCode":null,"errorMessage":"s3: sse-customer-key requires HTTPS endpoint (HTTP only allowed for localhost/private networks)","messagePattern":"s3: sse-customer-key requires HTTPS endpoint \\(HTTP only allowed for localhost/private networks\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"s3/replica_client.go","lineNumber":574,"sourceCode":"\t\t}\n\n\t\t// SSE-C requires HTTPS (except for localhost/private networks for testing)\n\t\tif c.Endpoint != \"\" {\n\t\t\tendpoint := c.Endpoint\n\t\t\tif !strings.HasPrefix(endpoint, \"http://\") && !strings.HasPrefix(endpoint, \"https://\") {\n\t\t\t\tendpoint = \"https://\" + endpoint\n\t\t\t}\n\t\t\tif strings.HasPrefix(endpoint, \"http://\") {\n\t\t\t\tu, err := url.Parse(endpoint)\n\t\t\t\tif err == nil {\n\t\t\t\t\thost := u.Hostname()\n\t\t\t\t\t// Allow localhost by name\n\t\t\t\t\tif host == \"localhost\" {\n\t\t\t\t\t\t// OK - localhost is allowed\n\t\t\t\t\t} else if ip := net.ParseIP(host); ip != nil && (ip.IsLoopback() || ip.IsPrivate()) {\n\t\t\t\t\t\t// OK - loopback (127.x.x.x) or private RFC1918 ranges (10.x, 172.16-31.x, 192.168.x)\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn fmt.Errorf(\"s3: sse-customer-key requires HTTPS endpoint (HTTP only allowed for localhost/private networks)\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// transportRetryMaxAttempts caps total attempts per operation; exponential\n// backoff between attempts still bounds request rate during outages.\nconst transportRetryMaxAttempts = 10\n\n// newTransportRetryer returns a retryer that keeps retrying through sustained\n// object-store transport flaps. The SDK's default token bucket (and the\n// adaptive mode previously configured here) only refills retry quota on\n// successful responses, so a sustained provider flap drains it to zero and\n// every subsequent operation fails fast (\"retry quota exceeded, 0 available\")","sourceCodeStart":556,"sourceCodeEnd":592,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/s3/replica_client.go#L556-L592","documentation":"SSE-C validation guard: SSE-C sends the encryption key in the request, so a plain-HTTP custom endpoint is only tolerated for localhost/loopback/private addresses. The configured http:// endpoint resolves to a public host and is rejected to prevent leaking the key in cleartext.","triggerScenarios":"Thrown at s3/replica_client.go:574 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use an https:// endpoint for the S3-compatible service","Or restrict endpoints to localhost/private IPs for testing"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}