projectdiscovery/nuclei · warning
first PL option must be VERSION
Error message
first PL option must be VERSION
What it means
The TDS pre-login specification requires VERSION (token 0x00) to be the first option in the table; the parser reads major/minor/build from its data. This error fires when tokens[0].token is anything other than 0x00 (e.g. ENCRYPTION 0x01 or INSTANCE 0x02 first), which no real SQL Server sends. It is one of the errNotMssql-wrapped parse failures, so mssql.IsMssql maps it to false and mssql.FingerprintMssql returns 'not a mssql service: first PL option must be VERSION'.
Source
Thrown at pkg/js/libs/mssql/fingerprint.go:232
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 {
case plTokenEncryption:
if len(tok.data) > 0 {
out.Encryption = int(tok.data[0])
out.EncryptionMode = encryptionModeName(tok.data[0])
}
case plTokenInstOpt:View on GitHub (pinned to 265b3a3dec)
Solutions
- Verify with mssql.IsMssql / a genuine TDS client before fingerprinting
- Hex-dump the response and decode the option table manually to see which token actually appears at byte 8
- If the server is a legitimate SQL Server variant, compare its raw reply against a stock instance to find the framing divergence
- Fall back to version detection via other probes (e.g. post-auth SELECT @@version once credentials exist)
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('first PL option must be VERSION')) log('non-conforming TDS peer: ' + host); else throw e; } Prevention
- Rely on IsMssql for classification; spec-violation replies are a definitive negative
- Compare failing hosts' raw replies against a stock SQL Server to diagnose emulators
When it happens
Trigger: mssql.FingerprintMssql / IsMssql against a reply whose option table starts with a non-VERSION token — non-standard TDS implementations, emulators, or corrupted/shifted byte streams where offsets desynchronize the table.
Common situations: Custom or embedded TDS-ish servers; honeypots; unit fixtures with options in the wrong order; replies where an earlier field shifted the parse position so the table is read from the wrong offset.
Related errors
- packet length mismatch: header=%d body=%d
- truncated PL option token
- invalid PL option data range
- missing PL option terminator
- no PL option tokens
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/4898c170a1630d42.
Report an issue: GitHub.