projectdiscovery/nuclei · warning
missing PL option terminator
Error message
missing PL option terminator
What it means
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'.
Source
Thrown at pkg/js/libs/mssql/fingerprint.go:226
return out, fmt.Errorf("truncated PL option token")
}
token := response[position]
offset := binary.BigEndian.Uint16(response[position+1 : position+3])
length := binary.BigEndian.Uint16(response[position+3 : position+5])
var data []byte
if length > 0 {
start := int(offset) + 8
end := start + int(length)
if start < 8 || end > len(response) {
return out, fmt.Errorf("invalid PL option data range")
}
data = response[start:end]
}
tokens = append(tokens, optionToken{token: token, offset: offset, length: length, data: data})
position += 5
}
if position >= len(response) || response[position] != tdsTerminator {
return out, fmt.Errorf("missing PL option terminator")
}
if len(tokens) == 0 {
return out, fmt.Errorf("no PL option tokens")
}
if tokens[0].token != plTokenVersion {
return out, fmt.Errorf("first PL option must be VERSION")
}
if len(tokens[0].data) < 4 {
return out, fmt.Errorf("VERSION option too short")
}
out.MajorVersion = int(tokens[0].data[0])
out.MinorVersion = int(tokens[0].data[1])
out.BuildNumber = int(tokens[0].data[2])<<8 | int(tokens[0].data[3])
out.Version = fmt.Sprintf("%d.%d.%d", out.MajorVersion, out.MinorVersion, out.BuildNumber)
for _, tok := range tokens[1:] {
switch tok.token {View on GitHub (pinned to 265b3a3dec)
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
Defensive patterns
Strategy: try-catch
Validate before calling
if (!mssql.IsMssql(host, port)) { return; } Try / catch
try { const info = mssql.FingerprintMssql(host, port); }
catch (e) { if (String(e).includes('missing PL option terminator')) log('unterminated TDS option table: ' + host); else throw e; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: Non-MSSQL services whose reply coincidentally starts with a valid TDS header; truncated TCP streams; malformed emulators; test fixtures missing the terminator byte.
Related errors
- packet length mismatch: header=%d body=%d
- truncated PL option token
- invalid PL option data range
- no PL option tokens
- first PL option must be VERSION
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/28f187521c23f246.
Report an issue: GitHub.