SEC: Fix LSP header injection via strict ASCII decoding
Replace errors='replace' with strict ascii.decode() in transport.py and server.py header parsing to prevent non-ASCII byte injection attacks. The replacement character (U+FFFD) could be exploited to craft malformed headers that bypass validation yet alter message framing per the LSP specification's ASCII-only header requirement. Closes #10608.
This commit is contained in:
@@ -169,13 +169,22 @@ class LspServer:
|
||||
raw_line = self._input.readline(MAX_HEADER_LINE_LENGTH)
|
||||
if not raw_line:
|
||||
return
|
||||
# SEC: Strict ASCII decoding prevents header injection attacks.
|
||||
# Using ``errors="replace"`` silently accepts non-ASCII bytes as
|
||||
# the replacement character (U+FFFD), which can mask malformed
|
||||
# headers and cause transport desync. The LSP spec requires
|
||||
# all HTTP-style headers to be plain ASCII. On decode errors,
|
||||
# ``continue`` keeps draining remaining header lines so that
|
||||
# the stream is correctly positioned at the blank terminator.
|
||||
try:
|
||||
decoded_line = raw_line.decode("ascii").rstrip("\r\n")
|
||||
except UnicodeDecodeError:
|
||||
except UnicodeDecodeError as exc:
|
||||
logger.warning(
|
||||
"lsp.transport.non_ascii_header_byte",
|
||||
raw_line=repr(raw_line),
|
||||
error=str(exc),
|
||||
)
|
||||
return None
|
||||
continue # keep draining; line already consumed by readline()
|
||||
line = decoded_line
|
||||
if line == "":
|
||||
return
|
||||
@@ -231,11 +240,18 @@ class LspServer:
|
||||
)
|
||||
self._drain_headers()
|
||||
return _SKIP
|
||||
# SEC: Strict ASCII decoding prevents header injection attacks.
|
||||
# Using ``errors="replace"`` silently accepts non-ASCII bytes as
|
||||
# the replacement character (U+FFFD), which can mask malformed
|
||||
# headers and cause transport desync. The LSP spec requires
|
||||
# all HTTP-style headers to be plain ASCII.
|
||||
try:
|
||||
decoded_line = raw_line.decode("ascii").rstrip("\r\n")
|
||||
except UnicodeDecodeError:
|
||||
except UnicodeDecodeError as exc:
|
||||
logger.warning(
|
||||
"lsp.transport.non_ascii_header_byte",
|
||||
raw_line=repr(raw_line),
|
||||
error=str(exc),
|
||||
)
|
||||
return _SKIP
|
||||
line = decoded_line
|
||||
|
||||
@@ -254,11 +254,19 @@ class StdioTransport:
|
||||
line = stdout.readline()
|
||||
if not line:
|
||||
return None # EOF — server exited
|
||||
# SEC: Strict ASCII decoding prevents header injection attacks.
|
||||
# Using ``errors="replace"`` silently accepts non-ASCII bytes as
|
||||
# the replacement character (U+FFFD), which an attacker could use
|
||||
# to craft malformed headers that bypass validation yet alter
|
||||
# message framing. The LSP specification requires all HTTP-style
|
||||
# headers to be plain ASCII; reject anything else immediately.
|
||||
try:
|
||||
decoded = line.decode("ascii").strip()
|
||||
except UnicodeDecodeError:
|
||||
except UnicodeDecodeError as exc:
|
||||
logger.warning(
|
||||
"lsp.transport.non_ascii_header_byte",
|
||||
raw_line=repr(line),
|
||||
error=str(exc),
|
||||
)
|
||||
return None
|
||||
if not decoded:
|
||||
|
||||
Reference in New Issue
Block a user