{"record":{"id":"e67203cd7eb135cb","repo":"wavetermdev/waveterm","slug":"offset-cannot-be-negative","errorCode":null,"errorMessage":"offset cannot be negative","messagePattern":"offset cannot be negative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/filestore/blockstore_cache.go","lineNumber":208,"sourceCode":"\t\t\tpartIdx = partIdx % maxPart\n\t\t}\n\t\tpartOffset := offset % partDataSize\n\t\tpartData := entry.getOrCreateDataCacheEntry(partIdx)\n\t\tnw, newDce := partData.writeToPart(partOffset, data)\n\t\tentry.DataEntries[partIdx] = newDce\n\t\tdata = data[nw:]\n\t\toffset += nw\n\t}\n\tif endWriteOffset > entry.File.Size || replace {\n\t\tentry.File.Size = endWriteOffset\n\t}\n\tentry.File.ModTs = time.Now().UnixMilli()\n}\n\n// returns (realOffset, data, error)\nfunc (entry *CacheEntry) readAt(ctx context.Context, offset int64, size int64, readFull bool) (int64, []byte, error) {\n\tif offset < 0 {\n\t\treturn 0, nil, fmt.Errorf(\"offset cannot be negative\")\n\t}\n\tfile, err := entry.loadFileForRead(ctx)\n\tif err != nil {\n\t\treturn 0, nil, err\n\t}\n\tif readFull {\n\t\tsize = file.Size - offset\n\t}\n\tif offset+size > file.Size {\n\t\tsize = file.Size - offset\n\t}\n\tif file.Opts.Circular {\n\t\trealDataOffset := int64(0)\n\t\tif file.Size > file.Opts.MaxSize {\n\t\t\trealDataOffset = file.Size - file.Opts.MaxSize\n\t\t}\n\t\tif offset < realDataOffset {\n\t\t\ttruncateAmt := realDataOffset - offset","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/filestore/blockstore_cache.go#L190-L226","documentation":"CacheEntry.readAt rejects negative offsets before loading the file. ReadAt is the main caller, but it can also be reached via compactIJson; the internal entry-level check catches offsets that the public wrapper did not pre-validate.","triggerScenarios":"Calling ReadAt with offset < 0, or compactIJson internally computing a negative offset from file state (e.g. a part offset calculation on a corrupt/oddly-sized file).","commonSituations":"Sentinel -1 offsets; offset arithmetic underflow in compaction; passing user-supplied offsets straight through without validation.","solutions":["Validate offset >= 0 before calling ReadAt or any compaction entry point.","If triggered from compactIJson, inspect the file's part map/size for corrupt metadata that yields negative offsets.","Clamp computed offsets (e.g. max(0, computed)) where arithmetic can underflow."],"exampleFix":"// before\nfs.ReadAt(ctx, zone, name, userOffset, 1024) // userOffset unchecked\n// after\nif userOffset < 0 { return fmt.Errorf(\"invalid offset\") }\nfs.ReadAt(ctx, zone, name, userOffset, 1024)","handlingStrategy":"validation","validationCode":"if offset < 0 { return fmt.Errorf(\"ReadAt: offset %d must be >= 0\", offset) }","typeGuard":"func validOffset(offset int64) bool { return offset >= 0 }","tryCatchPattern":null,"preventionTips":["Validate offsets at API boundaries before they reach filestore calls.","Clamp computed offsets (e.g. from compaction math) to >= 0.","Never encode 'not set' as a negative offset."],"tags":["filestore","validation","offset"],"backgroundTag":"invalid-argument-offset-negative","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}