{"record":{"id":"eae317081601034b","repo":"shadow1ng/fscan","slug":"mssql-truncated-token","errorCode":null,"errorMessage":"mssql: truncated token","messagePattern":"mssql: truncated token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/services/mssql_raw.go","lineNumber":386,"sourceCode":"\t\tif pos >= end {\n\t\t\treturn fmt.Errorf(\"mssql: truncated string in error token\")\n\t\t}\n\t\tlength := int(payload[pos]) * 2\n\t\tpos++\n\t\tif pos+length > end {\n\t\t\treturn fmt.Errorf(\"mssql: invalid string in error token\")\n\t\t}\n\t\tpos += length\n\t}\n\tif pos+4 > end {\n\t\treturn fmt.Errorf(\"mssql: truncated error line number\")\n\t}\n\treturn nil\n}\n\nfunc mssqlSkipLen16(payload []byte, pos int) (int, error) {\n\tif pos+2 > len(payload) {\n\t\treturn pos, fmt.Errorf(\"mssql: truncated token\")\n\t}\n\tsize := int(binary.LittleEndian.Uint16(payload[pos : pos+2]))\n\tnext := pos + 2 + size\n\tif next > len(payload) {\n\t\treturn pos, fmt.Errorf(\"mssql: invalid token size\")\n\t}\n\treturn next, nil\n}\n\nfunc mssqlReadUSVarChar(payload []byte, pos int) (string, int, error) {\n\tif pos+2 > len(payload) {\n\t\treturn \"\", pos, fmt.Errorf(\"mssql: truncated us varchar\")\n\t}\n\tchars := int(binary.LittleEndian.Uint16(payload[pos : pos+2]))\n\tpos += 2\n\tsize := chars * 2\n\tif pos+size > len(payload) {\n\t\treturn \"\", pos, fmt.Errorf(\"mssql: invalid us varchar size\")","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/plugins/services/mssql_raw.go#L368-L404","documentation":"mssqlSkipLen16 advances past a token whose body is a 2-byte length prefix (used for fixed-length login-response tokens). If fewer than 2 bytes remain, the length header itself cannot be read and the library throws 'truncated token'. It guards the token walker in mssqlParseLoginTokens against running off the end of a truncated payload.","triggerScenarios":"mssqlParseLoginTokens calls mssqlSkipLen16 for a token type whose payload starts at pos with pos+2 > len(payload).","commonSituations":"The login response was cut off mid-stream by a network drop or proxy timeout; a non-TDS service on port 1433 emitted a short blob; fuzzed server sends a payload ending right before a token header.","solutions":["Retry the connection; transient truncation is the most common cause.","Verify the port actually serves TDS SQL Server traffic.","Increase proxy/LB idle timeouts if large login responses are being cut.","Log the payload length and offset to confirm the response ended prematurely."],"exampleFix":"// before: skip token without checking header availability\nnext, err := mssqlSkipLen16(payload, pos)\n// after: bail out with context when the header is truncated\nif len(payload)-pos < 2 {\n    return fmt.Errorf(\"login response ended at offset %d, token header truncated\", pos)\n}\nnext, err := mssqlSkipLen16(payload, pos)","handlingStrategy":"try-catch","validationCode":"if pos+2 > len(payload) {\n    return fmt.Errorf(\"token header truncated: payload has %d bytes, need %d\", len(payload)-pos, 2)\n}","typeGuard":"func hasLen16Header(payload []byte, pos int) bool {\n    return pos+2 <= len(payload)\n}","tryCatchPattern":"next, err := mssqlSkipLen16(payload, pos)\nif err != nil {\n    conn.Close()\n    return fmt.Errorf(\"truncated token in login response: %w\", err)\n}","preventionTips":["Fully buffer the login response (all TDS packets) before parsing tokens.","Retry once on truncation; escalate if persistent.","Raise proxy/LB timeouts for long-running login handshakes.","Log payload size vs. consumed offset to detect systematic truncation."],"tags":["mssql","tds-protocol","truncated-payload","wire-parsing"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}