{"record":{"id":"cfd55a51ce20a0d3","repo":"JuliusBrussee/caveman","slug":"cachebench-invalid-trace-read-limits","errorCode":null,"errorMessage":"cachebench: invalid trace read limits","messagePattern":"cachebench: invalid trace read limits","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cacheengine/cachebench/trace.go","lineNumber":281,"sourceCode":"\tMaxLineBytes int\n\tMaxRecords   int\n\tMaxBodyBytes int\n}\n\n// DefaultTraceReadLimits supports public-corpus traces while bounding retained memory.\nfunc DefaultTraceReadLimits() TraceReadLimits {\n\treturn TraceReadLimits{MaxLineBytes: 96 << 20, MaxRecords: 100_000, MaxBodyBytes: 64 << 20}\n}\n\n// ReadTraceJSONL reads trace records using conservative default resource limits.\nfunc ReadTraceJSONL(reader io.Reader) ([]TraceRecord, error) {\n\treturn ReadTraceJSONLWithLimits(reader, DefaultTraceReadLimits())\n}\n\n// ReadTraceJSONLWithLimits reads strict trace JSONL under explicit resource limits.\nfunc ReadTraceJSONLWithLimits(reader io.Reader, limits TraceReadLimits) ([]TraceRecord, error) {\n\tif limits.MaxLineBytes <= 0 || limits.MaxLineBytes > 512<<20 || limits.MaxRecords <= 0 || limits.MaxRecords > 1_000_000 || limits.MaxBodyBytes <= 0 || limits.MaxBodyBytes > 256<<20 {\n\t\treturn nil, errors.New(\"cachebench: invalid trace read limits\")\n\t}\n\tscanner := bufio.NewScanner(reader)\n\tinitial := 64 * 1024\n\tif limits.MaxLineBytes < initial {\n\t\tinitial = limits.MaxLineBytes\n\t}\n\tscanner.Buffer(make([]byte, initial), limits.MaxLineBytes)\n\tseen := map[string]bool{}\n\tvar records []TraceRecord\n\tfor line := 1; scanner.Scan(); line++ {\n\t\traw := bytes.TrimSpace(scanner.Bytes())\n\t\tif len(raw) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif len(records) >= limits.MaxRecords {\n\t\t\treturn nil, fmt.Errorf(\"cachebench: trace exceeds record limit %d\", limits.MaxRecords)\n\t\t}\n\t\tif !validUniqueJSONObject(raw) {","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/cacheengine/cachebench/trace.go#L263-L299","documentation":"Returned by ReadTraceJSONLWithLimits in cacheengine/cachebench/trace.go when the caller-supplied TraceReadLimits fail validation: MaxLineBytes must be >0 and <=512MiB, MaxRecords must be >0 and <=1,000,000, and MaxBodyBytes must be >0 and <=256MiB. The library enforces these ceilings because trace parsing is resource-intensive and unbounded limits would allow memory exhaustion from a malformed or hostile trace file. It is a programmer-error guard, not a data error: the limits struct itself is invalid before any I/O happens.","triggerScenarios":"Calling cachebench.ReadTraceJSONLWithLimits(reader, limits) with any field zero/negative, or with MaxLineBytes > 512<<20, MaxRecords > 1_000_000, or MaxBodyBytes > 256<<20. Copying limits from user input or config without clamping, or building a partial struct (Go zero values make unset ints 0, which fails the <=0 check).","commonSituations":"Setting limits from a YAML/JSON config where a missing key deserializes as 0; passing math.MaxInt to 'disable' a cap; unit tests constructing TraceReadLimits with only one field set; upgrading from an older version that accepted arbitrary limits.","solutions":["Call ReadTraceJSONL(reader) instead, which passes DefaultTraceReadLimits() (MaxLineBytes 96MiB, MaxRecords 100k, MaxBodyBytes 64MiB) and is valid by construction","If custom limits are needed, clamp each field into range before the call: 0 < MaxLineBytes <= 512<<20, 0 < MaxRecords <= 1_000_000, 0 < MaxBodyBytes <= 256<<20","Log the limits struct values before calling so the offending field is obvious","If genuinely larger traces must be parsed, split the trace file and process in batches so MaxRecords stays under 1,000,000"],"exampleFix":"// before\nlimits := cachebench.TraceReadLimits{MaxLineBytes: 1 << 30} // only one field set; others are 0\nrecords, err := cachebench.ReadTraceJSONLWithLimits(r, limits)\n\n// after\nrecords, err := cachebench.ReadTraceJSONLWithLimits(r, cachebench.DefaultTraceReadLimits())\n// or clamp explicitly:\n// limits.MaxRecords = min(max(limits.MaxRecords, 1), 1_000_000) etc.","handlingStrategy":"validation","validationCode":"func validTraceLimits(l cachebench.TraceReadLimits) bool {\n\treturn l.MaxLineBytes > 0 && l.MaxLineBytes <= 512<<20 &&\n\t\tl.MaxRecords > 0 && l.MaxRecords <= 1_000_000 &&\n\t\tl.MaxBodyBytes > 0 && l.MaxBodyBytes <= 256<<20\n}\n\nif !validTraceLimits(limits) {\n\treturn fmt.Errorf(\"limits out of range: %+v\", limits)\n}\nrecords, err := cachebench.ReadTraceJSONLWithLimits(r, limits)","typeGuard":null,"tryCatchPattern":"records, err := cachebench.ReadTraceJSONLWithLimits(r, limits)\nif err != nil {\n\tif err.Error() == \"cachebench: invalid trace read limits\" {\n\t\treturn fmt.Errorf(\"config bug: trace limits invalid: %+v\", limits)\n\t}\n\treturn fmt.Errorf(\"read trace: %w\", err)\n}","preventionTips":["Prefer ReadTraceJSONL, which applies validated defaults","Centralize limit construction in one config-clamping helper instead of scattering literals","Never map optional config keys straight into TraceReadLimits; unset ints become 0 and fail"],"tags":["go","validation","resource-limits","configuration"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}