From 72012aea855ec0035c573f1da7b73cf27f8221df Mon Sep 17 00:00:00 2001 From: CleverThis Date: Sun, 17 May 2026 13:49:20 +0000 Subject: [PATCH] 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. --- src/cleveragents/lsp/server.py | 22 +++++++++++++++++++--- src/cleveragents/lsp/transport.py | 10 +++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/cleveragents/lsp/server.py b/src/cleveragents/lsp/server.py index a3a3c363d..e5679a915 100644 --- a/src/cleveragents/lsp/server.py +++ b/src/cleveragents/lsp/server.py @@ -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 diff --git a/src/cleveragents/lsp/transport.py b/src/cleveragents/lsp/transport.py index b9f903aa4..fedd55135 100644 --- a/src/cleveragents/lsp/transport.py +++ b/src/cleveragents/lsp/transport.py @@ -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: