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: