From 8287f2d1b78ac2eb4486e04b57fb7f87beb228ab Mon Sep 17 00:00:00 2001 From: Widthdom Date: Fri, 5 Jun 2026 13:33:04 +0900 Subject: [PATCH 1/3] Fix LSP textDocument URI length cap (#3129) --- USER_GUIDE.md | 5 ++++ changelog.d/unreleased/3129.security.md | 17 +++++++++++ src/CodeIndex/Lsp/LspServer.cs | 5 ++++ tests/CodeIndex.Tests/LspServerTests.cs | 39 +++++++++++++++++++++++++ 4 files changed, 66 insertions(+) create mode 100644 changelog.d/unreleased/3129.security.md diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 1c7cf7b0e4..e4ab3c7e65 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -1968,6 +1968,9 @@ server over stdio. It reuses the existing CodeIndex database and exposes `initialize`, `workspace/symbol`, `textDocument/documentSymbol`, `textDocument/definition`, and `textDocument/references` for editors that can launch an arbitrary LSP command but do not speak MCP. +Incoming `textDocument.uri` values are rejected before URI parsing when they +exceed 4096 characters, matching the MCP resource URI limit and keeping error +responses bounded. Tool results include structured JSON in `structuredContent` plus a short text summary in `content`, so AI tools can parse typed data without scraping large text blocks. @@ -4230,6 +4233,8 @@ cdidxには**MCP(Model Context Protocol)サーバー**が組み込まれて 任意の LSP command を起動できるが MCP には対応していない editor 向けに `initialize`、`workspace/symbol`、`textDocument/documentSymbol`、 `textDocument/definition`、`textDocument/references` を公開します。 +受信した `textDocument.uri` は 4096 文字を超える場合、URI parse の前に拒否されます。 +これは MCP resource URI の上限と揃えており、エラー応答が過大にならないようにします。 ツール結果は `structuredContent` に構造化JSON、`content` に短い要約テキストを返すため、AIツールは巨大なテキストをパースせずに型付きデータを扱えます。 diff --git a/changelog.d/unreleased/3129.security.md b/changelog.d/unreleased/3129.security.md new file mode 100644 index 0000000000..3d0eb17df1 --- /dev/null +++ b/changelog.d/unreleased/3129.security.md @@ -0,0 +1,17 @@ +--- +category: security +issues: + - 3129 +affected: + - src/CodeIndex/Lsp/LspServer.cs + - tests/CodeIndex.Tests/LspServerTests.cs + - USER_GUIDE.md +--- + +## English + +- **LSP text-document URIs are length-capped before parsing (#3129)** — LSP handlers now reject oversized `textDocument.uri` values before URI parsing or path normalization and keep the JSON-RPC error bounded. + +## 日本語 + +- **LSP の text-document URI を parse 前に長さ制限するようになりました (#3129)** — LSP handler は過大な `textDocument.uri` を URI parse や path normalization の前に拒否し、JSON-RPC error を bounded に保ちます。 diff --git a/src/CodeIndex/Lsp/LspServer.cs b/src/CodeIndex/Lsp/LspServer.cs index dd86becd23..9926f282c8 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.Mcp; using CodeIndex.Models; namespace CodeIndex.Lsp; @@ -15,6 +16,7 @@ internal sealed class LspServer : IDisposable internal const int MaxLspFrameBytes = 8 * 1024 * 1024; internal const int MaxLspHeaderLineBytes = 8 * 1024; internal const int MaxPositionDocumentBytes = 4 * 1024 * 1024; + internal const int MaxTextDocumentUriChars = McpBoundedText.MaxResourceUriChars; internal const int MaxJsonDepth = 32; private static readonly JsonDocumentOptions LspJsonDocumentOptions = new() { @@ -472,6 +474,9 @@ private static string GetDocumentPath(JsonElement root) var uri = GetString(root, "params", "textDocument", "uri"); if (string.IsNullOrWhiteSpace(uri)) throw new ArgumentException("textDocument.uri is required."); + if (uri.Length > MaxTextDocumentUriChars) + throw new ArgumentException( + $"textDocument.uri is too long. Max length is {MaxTextDocumentUriChars} characters; actual length is {uri.Length}."); return UriToPath(uri); } diff --git a/tests/CodeIndex.Tests/LspServerTests.cs b/tests/CodeIndex.Tests/LspServerTests.cs index 013906373e..7e4fa37ac6 100644 --- a/tests/CodeIndex.Tests/LspServerTests.cs +++ b/tests/CodeIndex.Tests/LspServerTests.cs @@ -276,6 +276,45 @@ public void HandleMessage_DocumentSymbol_ResolvesDuplicateBasenamesByRelativePat } } + [Fact] + public void HandleMessage_DocumentSymbol_RejectsOversizedTextDocumentUri_Issue3129() + { + var projectRoot = TestProjectHelper.CreateTempProject("cdidx_lsp_document_symbol_long_uri"); + 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 oversizedUri = "file:///" + new string('a', LspServer.MaxTextDocumentUriChars); + var request = JsonSerializer.Serialize(new + { + jsonrpc = "2.0", + id = 3129, + method = "textDocument/documentSymbol", + @params = new + { + textDocument = new { uri = oversizedUri }, + }, + }); + + var response = server.HandleMessage(request); + + Assert.NotNull(response); + var error = response!["error"]!; + Assert.Equal(-32602, error["code"]!.GetValue()); + var message = error["message"]!.GetValue(); + Assert.Equal( + $"textDocument.uri is too long. Max length is {LspServer.MaxTextDocumentUriChars} characters; actual length is {oversizedUri.Length}.", + message); + Assert.True(message.Length < 120); + Assert.DoesNotContain(oversizedUri, message, StringComparison.Ordinal); + } + finally + { + TestProjectHelper.DeleteDirectory(projectRoot); + } + } + [Fact] public void HandleMessage_Definition_ReturnsLocationForTokenAtPosition() { From d89eb3d08588f34ab4c6e77f9b99de6272e4d032 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Fri, 5 Jun 2026 14:22:16 +0900 Subject: [PATCH 2/3] Align LSP URI test with sanitized errors --- tests/CodeIndex.Tests/LspServerTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/CodeIndex.Tests/LspServerTests.cs b/tests/CodeIndex.Tests/LspServerTests.cs index fe3be6e421..7b316afd2a 100644 --- a/tests/CodeIndex.Tests/LspServerTests.cs +++ b/tests/CodeIndex.Tests/LspServerTests.cs @@ -372,9 +372,7 @@ public void HandleMessage_DocumentSymbol_RejectsOversizedTextDocumentUri_Issue31 var error = response!["error"]!; Assert.Equal(-32602, error["code"]!.GetValue()); var message = error["message"]!.GetValue(); - Assert.Equal( - $"textDocument.uri is too long. Max length is {LspServer.MaxTextDocumentUriChars} characters; actual length is {oversizedUri.Length}.", - message); + Assert.Equal("Invalid params", message); Assert.True(message.Length < 120); Assert.DoesNotContain(oversizedUri, message, StringComparison.Ordinal); } From c472ef7c93cd412b3d6d9d5fb154730c1c76c968 Mon Sep 17 00:00:00 2001 From: Widthdom Date: Fri, 5 Jun 2026 15:43:29 +0900 Subject: [PATCH 3/3] Align MCP audit request id test with validation cap (#3307) --- tests/CodeIndex.Tests/McpAuditLogTests.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/CodeIndex.Tests/McpAuditLogTests.cs b/tests/CodeIndex.Tests/McpAuditLogTests.cs index bcfe7c9c51..b05feca8e8 100644 --- a/tests/CodeIndex.Tests/McpAuditLogTests.cs +++ b/tests/CodeIndex.Tests/McpAuditLogTests.cs @@ -460,13 +460,12 @@ public void ToolsCall_CapsAuditArgumentKeyCount_Issue3237() } [Fact] - public void ToolsCall_TruncatesAuditRequestId_Issue3237() + public void ToolsCall_MaxValidRequestId_EmitsAuditRecord_Issue3307() { using var sink = new AuditLogSink(_auditPath, AuditLogSink.DefaultMaxBytes, includeValues: false); using var server = CreateServer(sink); - var id = new string('r', AuditLogSink.MaxRequestIdChars + 25); + var id = new string('r', McpServer.MaxRequestIdCharacterCount); var serializedId = JsonSerializer.Serialize(id); - var display = McpBoundedText.ForDisplay(serializedId, AuditLogSink.MaxRequestIdChars); var request = new JsonObject { ["jsonrpc"] = "2.0", @@ -481,12 +480,10 @@ public void ToolsCall_TruncatesAuditRequestId_Issue3237() _ = server.HandleMessage(request); - var rawLog = File.ReadAllText(_auditPath); - Assert.DoesNotContain(id, rawLog, StringComparison.Ordinal); var record = ReadOnlyRecord(); - Assert.Equal(display.Text, record.GetProperty("request_id").GetString()); - Assert.Equal(serializedId.Length, record.GetProperty("request_id_length").GetInt32()); - Assert.True(record.GetProperty("request_id_truncated").GetBoolean()); + Assert.Equal(serializedId, record.GetProperty("request_id").GetString()); + Assert.False(record.TryGetProperty("request_id_length", out _)); + Assert.False(record.TryGetProperty("request_id_truncated", out _)); } [Fact]