{"record":{"id":"28f187521c23f246","repo":"projectdiscovery/nuclei","slug":"missing-pl-option-terminator","errorCode":null,"errorMessage":"missing PL option terminator","messagePattern":"missing PL option terminator","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/js/libs/mssql/fingerprint.go","lineNumber":226,"sourceCode":"\t\t\treturn out, fmt.Errorf(\"truncated PL option token\")\n\t\t}\n\t\ttoken := response[position]\n\t\toffset := binary.BigEndian.Uint16(response[position+1 : position+3])\n\t\tlength := binary.BigEndian.Uint16(response[position+3 : position+5])\n\t\tvar data []byte\n\t\tif length > 0 {\n\t\t\tstart := int(offset) + 8\n\t\t\tend := start + int(length)\n\t\t\tif start < 8 || end > len(response) {\n\t\t\t\treturn out, fmt.Errorf(\"invalid PL option data range\")\n\t\t\t}\n\t\t\tdata = response[start:end]\n\t\t}\n\t\ttokens = append(tokens, optionToken{token: token, offset: offset, length: length, data: data})\n\t\tposition += 5\n\t}\n\tif position >= len(response) || response[position] != tdsTerminator {\n\t\treturn out, fmt.Errorf(\"missing PL option terminator\")\n\t}\n\tif len(tokens) == 0 {\n\t\treturn out, fmt.Errorf(\"no PL option tokens\")\n\t}\n\tif tokens[0].token != plTokenVersion {\n\t\treturn out, fmt.Errorf(\"first PL option must be VERSION\")\n\t}\n\tif len(tokens[0].data) < 4 {\n\t\treturn out, fmt.Errorf(\"VERSION option too short\")\n\t}\n\n\tout.MajorVersion = int(tokens[0].data[0])\n\tout.MinorVersion = int(tokens[0].data[1])\n\tout.BuildNumber = int(tokens[0].data[2])<<8 | int(tokens[0].data[3])\n\tout.Version = fmt.Sprintf(\"%d.%d.%d\", out.MajorVersion, out.MinorVersion, out.BuildNumber)\n\n\tfor _, tok := range tokens[1:] {\n\t\tswitch tok.token {","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/projectdiscovery/nuclei/blob/265b3a3dec374741614e342f813c10f8b38d2bb7/pkg/js/libs/mssql/fingerprint.go#L208-L244","documentation":"After walking the 5-byte pre-login option entries starting at byte 8, the parser must land exactly on the 0xff terminator byte. This error means the option table consumed the buffer to its end (position >= len) or the byte at the stop position is not 0xff, i.e. the table is unterminated or runs into unrelated data. Like the other pre-login parse failures it is wrapped with errNotMssql, so mssql.IsMssql returns false and mssql.FingerprintMssql returns 'not a mssql service: missing PL option terminator'.","triggerScenarios":"mssql.FingerprintMssql / IsMssql against a reply where option headers fill the packet exactly with no trailing 0xff, or where garbage follows the last option instead of the terminator.","commonSituations":"Non-MSSQL services whose reply coincidentally starts with a valid TDS header; truncated TCP streams; malformed emulators; test fixtures missing the terminator byte.","solutions":["Classify with mssql.IsMssql first — false here means 'reply failed TDS validation', which is the expected outcome for non-MSSQL ports","Verify with an independent TDS client (sqlcmd -S host,port) that real pre-login works","Capture and hex-dump the response; confirm byte stream ends ...option, 0xff within the declared length","Exclude the port from mssql fingerprinting and use generic network probes"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!mssql.IsMssql(host, port)) { return; }","typeGuard":null,"tryCatchPattern":"try { const info = mssql.FingerprintMssql(host, port); }\ncatch (e) { if (String(e).includes('missing PL option terminator')) log('unterminated TDS option table: ' + host); else throw e; }","preventionTips":["Pre-filter targets with IsMssql","Remember IsMssql returns false (not an error) for this parse failure","Use independent service detection for ports that repeatedly fail"],"tags":["mssql","tds","protocol-parsing","fingerprint","terminator","network"],"backgroundTag":null,"analyzedSha":"265b3a3dec374741614e342f813c10f8b38d2bb7","analyzedAt":"2026-08-15T20:05:51.855Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}