From b725d834f93c9a6f209abc698ba2dc3199463e24 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Fri, 5 Jun 2026 12:15:02 +0900 Subject: [PATCH 1/3] Fix LSP unknown method diagnostics for #3127 --- changelog.d/unreleased/3127.security.md | 16 ++++++++++++ src/CodeIndex/Lsp/LspServer.cs | 3 ++- tests/CodeIndex.Tests/LspServerTests.cs | 34 +++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 changelog.d/unreleased/3127.security.md diff --git a/changelog.d/unreleased/3127.security.md b/changelog.d/unreleased/3127.security.md new file mode 100644 index 0000000000..e29338e2e8 --- /dev/null +++ b/changelog.d/unreleased/3127.security.md @@ -0,0 +1,16 @@ +--- +category: security +issues: + - 3127 +affected: + - src/CodeIndex/Lsp/LspServer.cs + - tests/CodeIndex.Tests/LspServerTests.cs +--- + +## English + +- **LSP unknown method diagnostics truncate method names (#3127)** — unknown JSON-RPC method names now use bounded diagnostic text before being echoed in `Method not found` responses. + +## 日本語 + +- **LSP の未知 method 診断で method 名を切り詰めるようになりました (#3127)** — 未知 JSON-RPC method 名は `Method not found` response に埋め込む前に bounded diagnostic text へ変換されます。 diff --git a/src/CodeIndex/Lsp/LspServer.cs b/src/CodeIndex/Lsp/LspServer.cs index dd86becd23..04d4cf1d8e 100644 --- a/src/CodeIndex/Lsp/LspServer.cs +++ b/src/CodeIndex/Lsp/LspServer.cs @@ -5,6 +5,7 @@ using System.Text.Json.Nodes; using CodeIndex.Cli; using CodeIndex.Database; +using CodeIndex.Diagnostics; using CodeIndex.Models; namespace CodeIndex.Lsp; @@ -95,7 +96,7 @@ public int Run(Stream input, Stream output) "textDocument/documentSymbol" => Result(id, DocumentSymbol(root)), "textDocument/definition" => Result(id, Definition(root)), "textDocument/references" => Result(id, References(root)), - _ => hasId ? Error(id, -32601, $"Method not found: {method}") : null, + _ => hasId ? Error(id, -32601, $"Method not found: {DiagnosticSanitizer.ForMessage(method)}") : null, }; } catch (Exception ex) when (ex is ArgumentException or InvalidOperationException or JsonException or IOException) diff --git a/tests/CodeIndex.Tests/LspServerTests.cs b/tests/CodeIndex.Tests/LspServerTests.cs index 013906373e..ebc7c2a63f 100644 --- a/tests/CodeIndex.Tests/LspServerTests.cs +++ b/tests/CodeIndex.Tests/LspServerTests.cs @@ -105,6 +105,40 @@ public void HandleMessage_TooDeepJson_ReturnsParseError_Issue3021() } } + [Fact] + public void HandleMessage_UnknownMethod_TruncatesMethodName_Issue3127() + { + var projectRoot = TestProjectHelper.CreateTempProject("cdidx_lsp_unknown_method"); + try + { + var dbPath = TestProjectHelper.CreateProjectDb(projectRoot); + using var db = new DbContext(dbPath); + using var server = new LspServer(new DbReader(db), "1.2.3", ProgramRunner.CreateDefaultJsonOptions(), projectRoot); + var method = new string('m', 512) + "UNBOUNDED_SENTINEL"; + var request = JsonSerializer.Serialize(new + { + jsonrpc = "2.0", + id = 1, + method, + }); + + var response = server.HandleMessage(request); + + Assert.NotNull(response); + var error = response!["error"]!; + Assert.Equal(-32601, error["code"]!.GetValue()); + var message = error["message"]!.GetValue(); + Assert.StartsWith("Method not found: ", message, StringComparison.Ordinal); + Assert.EndsWith("...", message, StringComparison.Ordinal); + Assert.DoesNotContain("UNBOUNDED_SENTINEL", message, StringComparison.Ordinal); + Assert.True(message.Length <= "Method not found: ".Length + 243); + } + finally + { + TestProjectHelper.DeleteDirectory(projectRoot); + } + } + [Fact] public void Run_MalformedJsonFrame_WritesParseErrorAndContinues() { From 895e150aedf82c8e3fb0cefea2185e3a0b1a4e31 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Fri, 5 Jun 2026 12:38:30 +0900 Subject: [PATCH 2/3] Harden LSP unknown method diagnostics for #3127 --- src/CodeIndex/Lsp/LspServer.cs | 28 ++++++++++++++++++++++++- tests/CodeIndex.Tests/LspServerTests.cs | 4 ++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/CodeIndex/Lsp/LspServer.cs b/src/CodeIndex/Lsp/LspServer.cs index 04d4cf1d8e..a1a605c168 100644 --- a/src/CodeIndex/Lsp/LspServer.cs +++ b/src/CodeIndex/Lsp/LspServer.cs @@ -17,6 +17,7 @@ internal sealed class LspServer : IDisposable internal const int MaxLspHeaderLineBytes = 8 * 1024; internal const int MaxPositionDocumentBytes = 4 * 1024 * 1024; internal const int MaxJsonDepth = 32; + internal const int MaxUnknownMethodDiagnosticChars = 240; private static readonly JsonDocumentOptions LspJsonDocumentOptions = new() { MaxDepth = MaxJsonDepth, @@ -96,7 +97,7 @@ public int Run(Stream input, Stream output) "textDocument/documentSymbol" => Result(id, DocumentSymbol(root)), "textDocument/definition" => Result(id, Definition(root)), "textDocument/references" => Result(id, References(root)), - _ => hasId ? Error(id, -32601, $"Method not found: {DiagnosticSanitizer.ForMessage(method)}") : null, + _ => hasId ? Error(id, -32601, $"Method not found: {SanitizeUnknownMethod(method)}") : null, }; } catch (Exception ex) when (ex is ArgumentException or InvalidOperationException or JsonException or IOException) @@ -106,6 +107,31 @@ public int Run(Stream input, Stream output) } } + private static string SanitizeUnknownMethod(string method) + { + var wasTruncated = method.Length > MaxUnknownMethodDiagnosticChars; + var boundedMethod = wasTruncated ? method[..MaxUnknownMethodDiagnosticChars] : method; + try + { + var sanitized = DiagnosticSanitizer.ForMessage(boundedMethod); + return AppendEllipsisIfNeeded(sanitized, wasTruncated); + } + catch (System.Text.RegularExpressions.RegexMatchTimeoutException) + { + var fallback = boundedMethod + .Replace('\r', ' ') + .Replace('\n', ' ') + .Replace('\t', ' ') + .Trim(); + return AppendEllipsisIfNeeded(fallback, wasTruncated); + } + } + + private static string AppendEllipsisIfNeeded(string value, bool wasTruncated) + => wasTruncated && !value.EndsWith("...", StringComparison.Ordinal) + ? value + "..." + : value; + private JsonObject HandleShutdown(JsonNode? id) { _shutdownRequested = true; diff --git a/tests/CodeIndex.Tests/LspServerTests.cs b/tests/CodeIndex.Tests/LspServerTests.cs index ebc7c2a63f..63667d6e65 100644 --- a/tests/CodeIndex.Tests/LspServerTests.cs +++ b/tests/CodeIndex.Tests/LspServerTests.cs @@ -114,7 +114,7 @@ public void HandleMessage_UnknownMethod_TruncatesMethodName_Issue3127() var dbPath = TestProjectHelper.CreateProjectDb(projectRoot); using var db = new DbContext(dbPath); using var server = new LspServer(new DbReader(db), "1.2.3", ProgramRunner.CreateDefaultJsonOptions(), projectRoot); - var method = new string('m', 512) + "UNBOUNDED_SENTINEL"; + var method = new string('m', LspServer.MaxLspFrameBytes - 4096) + "UNBOUNDED_SENTINEL"; var request = JsonSerializer.Serialize(new { jsonrpc = "2.0", @@ -131,7 +131,7 @@ public void HandleMessage_UnknownMethod_TruncatesMethodName_Issue3127() Assert.StartsWith("Method not found: ", message, StringComparison.Ordinal); Assert.EndsWith("...", message, StringComparison.Ordinal); Assert.DoesNotContain("UNBOUNDED_SENTINEL", message, StringComparison.Ordinal); - Assert.True(message.Length <= "Method not found: ".Length + 243); + Assert.True(message.Length <= "Method not found: ".Length + LspServer.MaxUnknownMethodDiagnosticChars + "...".Length); } finally { From dd39e943e673add3a0fd774777b5c7bbea2a8cb4 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Fri, 5 Jun 2026 15:05:18 +0900 Subject: [PATCH 3/3] Preserve LSP method names for #3127 --- src/CodeIndex/Lsp/LspServer.cs | 21 ++++++------------- tests/CodeIndex.Tests/LspServerTests.cs | 28 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/CodeIndex/Lsp/LspServer.cs b/src/CodeIndex/Lsp/LspServer.cs index 1a5ddfe204..176d8a9d39 100644 --- a/src/CodeIndex/Lsp/LspServer.cs +++ b/src/CodeIndex/Lsp/LspServer.cs @@ -5,7 +5,6 @@ using System.Text.Json.Nodes; using CodeIndex.Cli; using CodeIndex.Database; -using CodeIndex.Diagnostics; using CodeIndex.Models; namespace CodeIndex.Lsp; @@ -119,20 +118,12 @@ private static string SanitizeUnknownMethod(string method) { var wasTruncated = method.Length > MaxUnknownMethodDiagnosticChars; var boundedMethod = wasTruncated ? method[..MaxUnknownMethodDiagnosticChars] : method; - try - { - var sanitized = DiagnosticSanitizer.ForMessage(boundedMethod); - return AppendEllipsisIfNeeded(sanitized, wasTruncated); - } - catch (System.Text.RegularExpressions.RegexMatchTimeoutException) - { - var fallback = boundedMethod - .Replace('\r', ' ') - .Replace('\n', ' ') - .Replace('\t', ' ') - .Trim(); - return AppendEllipsisIfNeeded(fallback, wasTruncated); - } + var sanitized = boundedMethod + .Replace('\r', ' ') + .Replace('\n', ' ') + .Replace('\t', ' ') + .Trim(); + return AppendEllipsisIfNeeded(sanitized, wasTruncated); } private static string AppendEllipsisIfNeeded(string value, bool wasTruncated) diff --git a/tests/CodeIndex.Tests/LspServerTests.cs b/tests/CodeIndex.Tests/LspServerTests.cs index 8d05a52afe..66802e21c7 100644 --- a/tests/CodeIndex.Tests/LspServerTests.cs +++ b/tests/CodeIndex.Tests/LspServerTests.cs @@ -139,6 +139,34 @@ public void HandleMessage_UnknownMethod_TruncatesMethodName_Issue3127() } } + [Fact] + public void HandleMessage_UnknownMethod_PreservesSlashDelimitedMethodName_Issue3127() + { + var projectRoot = TestProjectHelper.CreateTempProject("cdidx_lsp_unknown_method_slash"); + try + { + var dbPath = TestProjectHelper.CreateProjectDb(projectRoot); + using var db = new DbContext(dbPath); + using var server = new LspServer(new DbReader(db), "1.2.3", ProgramRunner.CreateDefaultJsonOptions(), projectRoot); + var request = JsonSerializer.Serialize(new + { + jsonrpc = "2.0", + id = 1, + method = "textDocument/hover", + }); + + var response = server.HandleMessage(request); + + Assert.NotNull(response); + Assert.Equal(-32601, response!["error"]!["code"]!.GetValue()); + Assert.Equal("Method not found: textDocument/hover", response["error"]!["message"]!.GetValue()); + } + finally + { + TestProjectHelper.DeleteDirectory(projectRoot); + } + } + [Fact] public void HandleMessage_InvalidParams_ReturnsStableErrorMessage_Issue3200() {