{"record":{"id":"a3f101cc3145d6e2","repo":"JuliusBrussee/caveman","slug":"s-s-trailing-pem-block-is-truncated-after-d","errorCode":null,"errorMessage":"%s (%s): trailing PEM block is truncated after %d certificate(s), so the bundle is incomplete and must not be half-trusted","messagePattern":"(.+?) \\((.+?)\\): trailing PEM block is truncated after (.+?) certificate\\(s\\), so the bundle is incomplete and must not be half-trusted","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/chhttp/chhttp.go","lineNumber":188,"sourceCode":"\trest := bundle\n\tfor {\n\t\tvar block *pem.Block\n\t\tblock, rest = pem.Decode(rest)\n\t\tif block == nil {\n\t\t\tbreak\n\t\t}\n\t\tif block.Type != \"CERTIFICATE\" {\n\t\t\tcontinue\n\t\t}\n\t\tcert, err := x509.ParseCertificate(block.Bytes)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%s (%s): certificate %d is unparseable, so the bundle is incomplete and must not be half-trusted: %w\", caFileEnv, path, added+1, err)\n\t\t}\n\t\troots.AddCert(cert)\n\t\tadded++\n\t}\n\tif bytes.Contains(rest, []byte(\"-----BEGIN\")) {\n\t\treturn nil, fmt.Errorf(\"%s (%s): trailing PEM block is truncated after %d certificate(s), so the bundle is incomplete and must not be half-trusted\", caFileEnv, path, added)\n\t}\n\tif added == 0 {\n\t\treturn nil, fmt.Errorf(\"%s (%s) contains no valid PEM certificate\", caFileEnv, path)\n\t}\n\treturn roots, nil\n}\n\n// errTransport fails every request with the configuration error that produced\n// it. A client constructor cannot return an error, and falling back to the\n// default transport would silently trade a rejected TLS configuration for\n// unpinned verification — so the client is built, and refuses to send.\ntype errTransport struct{ err error }\n\nfunc (t errTransport) RoundTrip(*http.Request) (*http.Response, error) { return nil, t.err }\n\n// queryTransport is the shared connection pool behind ClickHouse query clients.\n//\n// http.DefaultTransport keeps only DefaultMaxIdleConnsPerHost (2) idle","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/chhttp/chhttp.go#L170-L206","documentation":"Thrown by rootsWithCAFile (chhttp.go:188) after the PEM decode loop ends: the leftover bytes still contain a '-----BEGIN' marker, meaning the file ends with a PEM header whose block was never completed. A truncated trailing certificate would be silently dropped, producing a half-trusted pool, so the loader refuses the whole bundle instead. The count of successfully added certificates is included.","triggerScenarios":"The CA-file bundle's final PEM block is missing its END line, has a broken base64 body, or was cut off mid-write - pem.Decode returns nil for it, the loop breaks, and the remaining bytes still contain '-----BEGIN'.","commonSituations":"File truncated by a partial download or a full disk during generation; editor save that dropped the last lines; cat of two files where the second was still being written; a bundle piped through a command whose output was limited (head, log truncation).","solutions":["Check the tail of the bundle: the last block must end with a complete -----END CERTIFICATE----- line followed by a newline.","Re-download or regenerate the bundle from the source CA and compare checksums.","Strip the truncated trailing block and re-test, or restore from a known-good copy.","Add a CI check that validates the bundle end-to-end (openssl verifies every block) before deployment."],"exampleFix":"# before: truncated tail\n-----BEGIN CERTIFICATE-----\nMIIB...(cut off mid-bas\n\n# after: complete block\n-----BEGIN CERTIFICATE-----\nMIIB...full base64...\n-----END CERTIFICATE-----\n","handlingStrategy":"validation","validationCode":"// after decoding all blocks, leftover '-----BEGIN' means truncation\nrest := data\nfor {\n    var blk *pem.Block\n    blk, rest = pem.Decode(rest)\n    if blk == nil { break }\n}\nif bytes.Contains(rest, []byte(\"-----BEGIN\")) {\n    return fmt.Errorf(\"bundle truncated: trailing PEM header without complete block\")\n}","typeGuard":null,"tryCatchPattern":"err := validateBundle(path)\nif err != nil {\n    log.Fatalf(\"config: %v\", err) // treat as fatal: refuse partial trust\n}","preventionTips":["Write bundles atomically (temp file + rename) so readers never see partial writes.","Checksum downloads and compare against the source.","End every PEM file with a newline after the END line."],"tags":["go","tls","certificates","pem","truncation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}