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:
2026-05-17 13:49:20 +00:00
parent 8bbb5bb1e9
commit 72012aea85
2 changed files with 28 additions and 4 deletions
+19 -3
View File
@@ -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
+9 -1
View File
@@ -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: