You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
High — language-server availability and bounded memory behavior
Context
The GOWDK language server uses stdio JSON-RPC/LSP framing. internal/lsp/rpc.go currently reads headers with bufio.Reader.ReadString('\n'), parses Content-Length with strconv.Atoi, allocates the declared body directly, and then calls io.ReadFull:
The server uses full document synchronization and stores each open document's complete text in Server.documents.
LSP normally runs as a trusted local editor child process, but editor bugs, extension conflicts, malformed clients, accidental large-file opens, or corrupted framing should not be able to force an unbounded allocation or wedge the server indefinitely.
Problem
The current framing path has several failure modes:
Arbitrary body allocation. A client can declare a very large positive Content-Length, causing make([]byte, contentLength) before the payload is received.
Unbounded header lines.ReadString('\n') can accumulate a header line of arbitrary size.
Unbounded total headers. The loop has no aggregate byte or header-count ceiling.
Ambiguous duplicate lengths. Multiple Content-Length headers overwrite the previous value rather than requiring one value or identical duplicates.
Session stalls. A client can declare a large body and stop writing, leaving the single-threaded message loop waiting indefinitely until the pipe closes.
Aggregate document retention. Many open documents or a few unusually large files can retain an unbounded amount of text in memory.
Poor failure classification. Framing errors, oversized messages, invalid JSON, and invalid LSP parameters do not have a clearly documented session/recovery policy.
Potential noisy logging. Diagnostics for malformed messages must avoid dumping arbitrary client payload contents.
This issue is about protocol and memory bounds. Request cancellation and concurrent/background analysis can be addressed separately.
Goal
Define and enforce a bounded LSP transport and document-memory contract that handles malformed or oversized input deterministically without allocating according to untrusted lengths.
Proposed design
1. Add explicit server limits
Introduce internal/configurable limits with conservative defaults, for example:
The exact values should be based on realistic GOWDK files and common editor behavior. Defaults should be generous enough for ordinary projects while remaining finite.
Expose constructor options for tests and embedding. A CLI flag is not necessarily required in the first implementation; environment/config overrides should only be added if users demonstrate a need.
2. Bound header parsing
Replace unbounded ReadString framing with a limited parser that:
caps each line;
caps aggregate header bytes;
caps header count;
accepts CRLF and the documented tolerant newline behavior intentionally;
rejects malformed header syntax cleanly;
treats header names case-insensitively;
rejects negative, overflowed, empty, or non-decimal content lengths;
requires exactly one effective Content-Length value;
rejects conflicting duplicates;
ignores or handles known optional headers according to LSP/JSON-RPC rules.
The parser must not allocate a buffer proportional to an over-limit line before rejecting it.
3. Validate length before allocation
Parse the length into a bounded integer type and compare it with MaxMessageBytes before allocating.
For body-size and framing errors, document whether the session terminates or attempts recovery.
Terminating the stdio session is often safer after an oversized or ambiguous frame because continuing requires consuming exactly the rejected body length, which can itself block or process an enormous stream. Do not continue from an unknown framing boundary.
When the body is within limits but JSON is malformed, preserve the current JSON-RPC parse-error response and continue where safe.
The server should log a concise code and size metadata, not the raw oversized body.
5. Bound open-document state
Before accepting didOpen or full-text didChange:
reject or degrade analysis for a document exceeding MaxDocumentBytes;
enforce a total retained-text budget across open documents;
enforce a maximum open-document count or use an eviction/degraded-analysis policy;
update aggregate accounting correctly on open, change, close, and duplicate notifications;
do not replace the last valid document snapshot with an over-limit update;
publish or log a clear editor-facing message explaining that analysis was skipped due to size.
The policy should distinguish transport message size from document text size: a valid message may contain other fields and therefore require a slightly larger transport budget.
6. Avoid unnecessary copies
Audit the decode/update path for duplicate full-text copies. Full-sync LSP inherently receives a complete string, but the implementation should avoid retaining both raw JSON and multiple long-lived copies after handling.
A future incremental-sync implementation is outside this issue, but the bounded design should not prevent it.
7. Add observability without content leakage
Optional debug logs may record:
request method;
declared and actual bytes;
active document count and retained bytes;
rejection code;
document URI/path only according to existing logging policy.
Never log complete source text or malformed raw JSON by default.
Client behavior
For an oversized document received in a valid request/notification, use standard LSP mechanisms where practical:
window/logMessage or window/showMessage for an actionable explanation;
empty/cleared diagnostics for a document no longer analyzed;
no server crash or uncontrolled allocation.
For fatal framing errors where a valid JSON-RPC ID is unavailable, log and close the session rather than inventing a response that may further desynchronize the stream.
Test plan
Add deterministic transport tests covering:
ordinary request and notification frames;
CRLF framing;
header name casing;
body exactly at the limit;
body one byte over the limit without proportional allocation;
extremely large decimal Content-Length;
integer overflow;
negative and signed lengths;
missing length;
empty length;
duplicate identical lengths;
duplicate conflicting lengths;
over-limit header line;
over-limit aggregate headers;
too many headers;
truncated body;
malformed JSON inside a valid bounded frame;
recovery after a nonfatal JSON parse error;
documented termination after a fatal framing error;
a reader that supplies data in one-byte chunks;
no raw body leakage in error logs.
Add document-state tests covering:
document exactly at the limit;
over-limit didOpen;
valid open followed by over-limit didChange preserving the valid snapshot;
aggregate open-document budget;
accounting after didClose;
repeated open/change/close without counter drift;
large UTF-8 text where byte and character counts differ;
clear editor-facing notification when analysis is skipped.
Add fuzzing for the bounded header parser and frame decoder. Fuzz assertions should include no panic and no allocation proportional to attacker-declared lengths beyond configured limits.
Acceptance criteria
LSP header lines, aggregate headers, header count, and message bodies have finite documented limits.
Content-Length is validated for syntax, overflow, duplicates, and maximum size before allocation.
Oversized or ambiguous frames cannot force allocation proportional to their declared length.
Fatal versus recoverable framing behavior is explicit and tested.
Open-document count, per-document bytes, and aggregate retained text are bounded.
An over-limit document/update does not corrupt or replace the last valid snapshot.
The editor receives an actionable explanation when analysis is skipped for size.
Error/debug logs do not dump source text or malformed message bodies.
Header/frame parsing has focused fuzz coverage.
Existing normal initialize, diagnostics, completion, hover, navigation, formatting, and shutdown flows remain compatible.
Limits can be overridden in tests/embedding without global mutable state.
Non-goals
Implementing incremental text synchronization.
Parallelizing request execution.
Adding $ /cancelRequest support.
Supporting arbitrarily large files with full semantic analysis.
Treating the local editor as a hostile remote network peer; the goal is bounded failure, not a network-service sandbox.
Priority
High — language-server availability and bounded memory behavior
Context
The GOWDK language server uses stdio JSON-RPC/LSP framing.
internal/lsp/rpc.gocurrently reads headers withbufio.Reader.ReadString('\n'), parsesContent-Lengthwithstrconv.Atoi, allocates the declared body directly, and then callsio.ReadFull:There is no explicit limit on:
The server uses full document synchronization and stores each open document's complete text in
Server.documents.LSP normally runs as a trusted local editor child process, but editor bugs, extension conflicts, malformed clients, accidental large-file opens, or corrupted framing should not be able to force an unbounded allocation or wedge the server indefinitely.
Problem
The current framing path has several failure modes:
Content-Length, causingmake([]byte, contentLength)before the payload is received.ReadString('\n')can accumulate a header line of arbitrary size.Content-Lengthheaders overwrite the previous value rather than requiring one value or identical duplicates.This issue is about protocol and memory bounds. Request cancellation and concurrent/background analysis can be addressed separately.
Goal
Define and enforce a bounded LSP transport and document-memory contract that handles malformed or oversized input deterministically without allocating according to untrusted lengths.
Proposed design
1. Add explicit server limits
Introduce internal/configurable limits with conservative defaults, for example:
The exact values should be based on realistic GOWDK files and common editor behavior. Defaults should be generous enough for ordinary projects while remaining finite.
Expose constructor options for tests and embedding. A CLI flag is not necessarily required in the first implementation; environment/config overrides should only be added if users demonstrate a need.
2. Bound header parsing
Replace unbounded
ReadStringframing with a limited parser that:Content-Lengthvalue;The parser must not allocate a buffer proportional to an over-limit line before rejecting it.
3. Validate length before allocation
Parse the length into a bounded integer type and compare it with
MaxMessageBytesbefore allocating.Use an error such as:
Suggested stable codes:
lsp_header_too_large;lsp_too_many_headers;lsp_missing_content_length;lsp_conflicting_content_length;lsp_invalid_content_length;lsp_message_too_large;lsp_truncated_message.4. Define session recovery behavior
For body-size and framing errors, document whether the session terminates or attempts recovery.
Terminating the stdio session is often safer after an oversized or ambiguous frame because continuing requires consuming exactly the rejected body length, which can itself block or process an enormous stream. Do not continue from an unknown framing boundary.
When the body is within limits but JSON is malformed, preserve the current JSON-RPC parse-error response and continue where safe.
The server should log a concise code and size metadata, not the raw oversized body.
5. Bound open-document state
Before accepting
didOpenor full-textdidChange:MaxDocumentBytes;The policy should distinguish transport message size from document text size: a valid message may contain other fields and therefore require a slightly larger transport budget.
6. Avoid unnecessary copies
Audit the decode/update path for duplicate full-text copies. Full-sync LSP inherently receives a complete string, but the implementation should avoid retaining both raw JSON and multiple long-lived copies after handling.
A future incremental-sync implementation is outside this issue, but the bounded design should not prevent it.
7. Add observability without content leakage
Optional debug logs may record:
Never log complete source text or malformed raw JSON by default.
Client behavior
For an oversized document received in a valid request/notification, use standard LSP mechanisms where practical:
window/logMessageorwindow/showMessagefor an actionable explanation;For fatal framing errors where a valid JSON-RPC ID is unavailable, log and close the session rather than inventing a response that may further desynchronize the stream.
Test plan
Add deterministic transport tests covering:
Content-Length;Add document-state tests covering:
didOpen;didChangepreserving the valid snapshot;didClose;Add fuzzing for the bounded header parser and frame decoder. Fuzz assertions should include no panic and no allocation proportional to attacker-declared lengths beyond configured limits.
Acceptance criteria
Content-Lengthis validated for syntax, overflow, duplicates, and maximum size before allocation.Non-goals
$ /cancelRequestsupport.Related
gowdk.config.go#678 — project-independent file checking