{"record":{"id":"252e8c79586e5732","repo":"JuliusBrussee/caveman","slug":"clickhouse-response-byte-limit-must-be-positive","errorCode":null,"errorMessage":"clickhouse response byte limit must be positive","messagePattern":"clickhouse response byte limit must be positive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/chhttp/chhttp.go","lineNumber":51,"sourceCode":"\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/JuliusBrussee/caveman/shared/platform/env\"\n)\n\nconst (\n\tdefaultInsertTimeoutMS = 5000\n\tdefaultQueryTimeoutMS  = 30000\n)\n\n// ReadBodyBounded reads an HTTP response/error body with a hard byte ceiling.\n// It deliberately reads one byte beyond the limit so callers can distinguish a\n// truncated body from an exact-limit body and fail closed before parsing or\n// logging attacker-controlled content.\nfunc ReadBodyBounded(r io.Reader, maxBytes int64) ([]byte, error) {\n\tif maxBytes <= 0 {\n\t\treturn nil, fmt.Errorf(\"clickhouse response byte limit must be positive\")\n\t}\n\tdata, err := io.ReadAll(io.LimitReader(r, maxBytes+1))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif int64(len(data)) > maxBytes {\n\t\treturn nil, fmt.Errorf(\"clickhouse response exceeds %d byte limit\", maxBytes)\n\t}\n\treturn data, nil\n}\n\n// The TLS knobs. All three are unset by default, in which case the clients keep\n// stock net/http behaviour: system roots, hostname verified against the URL host.\nconst (\n\t// serverNameEnv overrides tls.Config.ServerName. This is the managed-\n\t// ClickHouse cutover case: the private .internal DNS record is dialled while\n\t// the deployment's certificate carries only *.dtwh SANs, so verification must\n\t// run against the name the certificate actually holds. Chain AND hostname","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/shared/platform/chhttp/chhttp.go#L33-L69","documentation":"ReadBodyBounded was called with a maxBytes ceiling of zero or less, which cannot bound anything. It is a caller bug (an unset limit variable) — the guard fires before any reading so an unbounded read never happens by accident of a missing limit.","triggerScenarios":"Thrown at shared/platform/chhttp/chhttp.go:51 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Pass a positive byte limit to ReadBodyBounded","Default the limit variable from config when unset instead of passing 0"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}