{"record":{"id":"2942521d4a7a52c8","repo":"microsoft/typescript-go","slug":"invalid-ast-header-offsets-expected-strtable-s","errorCode":null,"errorMessage":"invalid AST header offsets: expected strTable <= strData <= extData <= nodeOff (got %d, %d, %d, %d)","messagePattern":"invalid AST header offsets: expected strTable <= strData <= extData <= nodeOff \\(got (.+?), (.+?), (.+?), (.+?)\\)","errorType":"validation","errorClass":"ErrClientError","httpStatus":null,"severity":"error","filePath":"internal/api/encoder/decoder.go","lineNumber":76,"sourceCode":"\tif version != ProtocolVersion {\n\t\treturn nil, fmt.Errorf(\"unsupported protocol version %d (expected %d)\", version, ProtocolVersion)\n\t}\n\n\tstrTable := readLE32(data, HeaderOffsetStringOffsets)\n\tstrData := readLE32(data, HeaderOffsetStringData)\n\textData := readLE32(data, HeaderOffsetExtendedData)\n\tnodeOff := readLE32(data, HeaderOffsetNodes)\n\n\tdataLen := uint32(len(data))\n\n\t// Validate that all offsets are within the buffer.\n\tif strTable > dataLen || strData > dataLen || extData > dataLen || nodeOff > dataLen {\n\t\treturn nil, fmt.Errorf(\"invalid AST header offsets: offsets exceed data length (%d)\", dataLen)\n\t}\n\n\t// Validate monotonic non-decreasing order of regions.\n\tif !(strTable <= strData && strData <= extData && extData <= nodeOff) {\n\t\treturn nil, fmt.Errorf(\"invalid AST header offsets: expected strTable <= strData <= extData <= nodeOff (got %d, %d, %d, %d)\", strTable, strData, extData, nodeOff)\n\t}\n\n\td := &astDecoder{\n\t\traw:      data,\n\t\tstrTable: strTable,\n\t\tstrData:  strData,\n\t\textData:  extData,\n\t\tnodeOff:  nodeOff,\n\t\tfactory:  ast.NewNodeFactory(ast.NodeFactoryHooks{}),\n\t}\n\n\td.nodeCount = (len(data) - int(d.nodeOff)) / NodeSize\n\n\t// Convert entire string data region to a single Go string upfront.\n\t// Substringing a Go string shares the backing array, so subsequent\n\t// getString calls produce substrings with zero allocations.\n\td.allStringData = string(data[d.strData:])\n","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/encoder/decoder.go#L58-L94","documentation":"The four region offsets are individually in-bounds but violate the required layout strTable <= strData <= extData <= nodeOff. The format mandates contiguous ordered regions, so violation means the header bytes are effectively garbage rather than a decoder bug.","triggerScenarios":"Random or text bytes that pass the length check; a blob whose header alone was overwritten; endianness mistakes in a non-reference producer writing big-endian offsets.","commonSituations":"Decoding a file that was never an encoded AST (config, log) due to a path mix-up; memory corruption in fuzzing harnesses; byte-swapped copies through tools that assume big-endian.","solutions":["Confirm you opened the artifact actually written by the encoder, not a same-named file of another format","Regenerate the blob from the parsed source file","If fuzzing, treat as invalid input and skip - both header checks exist to reject this safely","Add a magic/version prefix to your cache entries so wrong-format files fail earlier"],"exampleFix":"// before\nblob, _ := os.ReadFile(cachePath) // may be any file\n\n// after\nblob, _ := os.ReadFile(cachePath)\nif len(blob) < 4 || string(blob[:4]) != \"tsast\" { return errors.New(\"not an AST blob\") }","handlingStrategy":"validation","validationCode":"o1 := binary.LittleEndian.Uint32(data[encoder.HeaderOffsetStringOffsets:])\no2 := binary.LittleEndian.Uint32(data[encoder.HeaderOffsetStringData:])\no3 := binary.LittleEndian.Uint32(data[encoder.HeaderOffsetExtendedData:])\no4 := binary.LittleEndian.Uint32(data[encoder.HeaderOffsetNodes:])\nif !(o1 <= o2 && o2 <= o3 && o3 <= o4) { return errNotASTBlob }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Tag cache entries with a magic prefix so wrong-format files fail earlier","Never feed files of unknown provenance to the decoder","Regenerate suspect blobs from source instead of patching them"],"tags":["go","corruption","header","binary","offsets","wrong-format"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}