Skip to content

High: byte-index truncation of LSP error message can panic and abort the process #294

Description

@bug-ops

Vulnerability

lsp/client.rs truncates an LSP server's JSON-RPC error message field by byte length before logging and returning it, using a raw byte-index slice instead of a UTF-8-safe boundary:

let message = if error.message.len() > 200 {
    format!("{}... (truncated)", &error.message[..200])
} else {
    error.message.clone()
};

error.message comes directly from the spawned LSP server's JSON-RPC error response and is not validated for length in a char-boundary-safe way before slicing.

Severity

High — the workspace sets panic = "abort" (Cargo.toml:61), so any reachable panic terminates the entire mcpls process rather than just the failing task, escalating what would otherwise be a Medium/DoS-scoped finding into a full outage of every active LSP session and in-flight MCP tool call.

Location

crates/mcpls-core/src/lsp/client.rs:541-542

Attack Scenario

A malicious or compromised LSP server (or a language server responding to attacker-controlled workspace content, e.g. a crafted symbol/file name it echoes back in an error) returns a JSON-RPC error whose message field contains multi-byte UTF-8 characters positioned so that byte offset 200 falls in the middle of a character. &error.message[..200] panics with "byte index 200 is not a char boundary". Because panic = "abort" is set, this aborts the whole mcpls process, dropping every language server connection and every pending MCP tool call — a single malformed error response from one LSP server takes down the bridge for all servers and all clients.

Remediation

Truncate on a char boundary instead of a raw byte index, e.g.:

let message = if error.message.len() > 200 {
    let cut = error.message
        .char_indices()
        .map(|(i, _)| i)
        .take_while(|&i| i <= 200)
        .last()
        .unwrap_or(0);
    format!("{}... (truncated)", &error.message[..cut])
} else {
    error.message.clone()
};

Or use .chars().take(200).collect::<String>().

References

CWE-248 (Uncaught Exception), CWE-20 (Improper Input Validation)

Metadata

Metadata

Assignees

Labels

P1High: degraded UX, incorrect non-destructive behaviorbugSomething isn't workingmcpls-coremcpls-core crate changes

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions