Safeguard
Supply Chain Security•

Language Server Protocol (LSP) Explained: JSON-RPC and Editor Trust

LSP is the JSON-RPC protocol editors use to talk to language servers. How its framing, capabilities and 3.18 work, and why workspace trust matters.

Safeguard Security Team
31 min read

The Language Server Protocol (LSP) is the protocol an editor or IDE uses to talk to a language server, a separate process that supplies completion, go to definition, find references and diagnostics. Messages are JSON-RPC 2.0, framed with a Content-Length header and usually carried over the server's standard input and output.

The current version on the official specification site, checked on 23 October 2026, is 3.18, with a change log date of 06/04/2026; 3.17 (05/10/2022) is now labelled the previous version. This guide covers the wire format, what changed across 3.x, and the part most explainers skip: a language server reads, and often builds, whatever repository you open, which is why editors added workspace trust.

Key takeaways

  • LSP moves language smarts out of the editor, so M editors and N languages need M plus N integrations instead of M times N.
  • Each message is an ASCII header with a required Content-Length in bytes, a blank line, then one UTF-8 JSON-RPC 2.0 message. Batches are not supported.
  • Sessions start with initialize (a capability exchange) and initialized, and end with shutdown and exit.
  • LSP has no authentication, encryption or authorization. It assumes the client started a trusted local process.
  • The real risk is what the server does with your project. VS Code's documentation warns that a language extension "may execute code from the currently loaded workspace".
  • VS Code's Workspace Trust opens unfamiliar folders in Restricted Mode, limiting extensions, tasks, debugging, the terminal and AI agents. It cannot stop a malicious extension.
  • LSP, the Debug Adapter Protocol (DAP) and the Model Context Protocol (MCP) share one design idea. AI coding tools now use all three.

What LSP is for, where it sits and which document defines it

The M times N problem

Before LSP, every editor that wanted completion or go to definition for a language built it against its own plugin API. The official overview says this work "must be repeated for each development tool, as each provides different APIs for implementing the same features." With M editors and N languages, that is M times N integrations.

LSP puts the language knowledge in a server that speaks a standard protocol. Each language needs one server and each editor one LSP client, so the total becomes M plus N. The trick is the data model: LSP talks in document URIs and cursor positions, not syntax trees or compiler symbols. A URI and a line and character offset mean the same thing in every language, so the protocol stays small.

Clients, servers and who starts whom

RoleWhat it isExamples
ClientThe development tool that wants code intelligenceVS Code, Neovim, Emacs, Eclipse, or a coding agent such as Claude Code
ServerA process that provides language featuresgopls, clangd, typescript-language-server

The specification says the client owns the server's life: "It is up to the client to decide when to start (process-wise) and when to shutdown a server." In practice the editor, or one of its extensions, launches the server as a child process the moment you open a matching file. The user rarely sees it happen, which matters later.

Where it sits in the stack

LSP is an application layer protocol with no transport of its own. The specification recommends servers support four channels, picked by command line argument: stdio; pipe (named pipes on Windows, socket files on Linux and macOS); socket, where "the port is passed as the next arg", so there is no default port; and node IPC, only when both sides run under Node.js. It also recommends passing the editor's process ID so the server can exit if the editor dies.

The defining documents

LSP is not an IETF, ISO or W3C standard. The normative text is the specification Microsoft publishes at microsoft.github.io/language-server-protocol, one page per version, with data structures written as TypeScript interfaces. Since 3.17 it also ships a machine readable meta model.

DocumentWhat it covers
LSP specification 3.18Current version: base protocol, lifecycle, every message
LSP specification 3.17Previous version, still the baseline many servers target
Base Protocol 0.9Header, JSON-RPC structures, capabilities and lifecycle as a separate, experimental spec
JSON-RPC 2.0 (jsonrpc.org, origin 2010-03-26, updated 2013-01-04)The message format

LSP obsoletes no external standard. Within the 3.x line, later versions deprecate fields: rootPath in favour of rootUri, and rootUri in favour of workspaceFolders.

How LSP works on the wire

The base protocol: headers and content

Every message has a header part and a content part, "comparable to HTTP". Header fields are a name, a colon and space, and a value, each ending in CRLF; an empty line ends the header.

Header fieldTypeMeaning
Content-LengthnumberLength of the content in bytes. Required.
Content-TypestringDefaults to application/vscode-jsonrpc; charset=utf-8. Optional.

The header is ASCII. The content defaults to UTF-8, which the spec says "is the only encoding supported right now"; implementations should also accept the older, incorrect utf8 label. A framed request (length shown as a placeholder):

Content-Length: ...\r\n
\r\n
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "textDocument/completion",
  "params": { ... }
}

The reader reads header lines until the empty one, reads exactly that many bytes, then decodes JSON. There is no delimiter inside the content and no checksum, so a wrong byte count or stray output on the stream loses both sides their place.

JSON-RPC 2.0 inside the frame

JSON-RPC describes itself as "a stateless, light-weight remote procedure call (RPC) protocol" that is transport agnostic. LSP uses three shapes:

MessageFieldsRule
Requestjsonrpc ("2.0"), id (integer or string), method, optional paramsEvery processed request gets a response
Responsejsonrpc, id, exactly one of result or errorNo result value means result is null
Notificationjsonrpc, method, optional params, no idMust not be answered

LSP adds two conventions: params should be objects rather than positional arrays, and the protocol "currently does not support JSON-RPC batch messages". Both sides send requests; LSP is not a one way query interface. Methods starting with $/ are implementation dependent: a peer may ignore such notifications and must answer such a request it does not handle with MethodNotFound (-32601).

Lifecycle: initialize, initialized, shutdown, exit

Client (editor)                                   Server (language server)
     |  starts server process (stdio, pipe, socket or node IPC)
     |--- initialize (processId, rootUri, workspaceFolders,
     |                capabilities, initializationOptions, trace) -->|
     |<-- InitializeResult (server capabilities, serverInfo) ---------|
     |--- initialized (notification) -------------------------------->|
     |<-- client/registerCapability (optional) ----------------------|
     |--- textDocument/didOpen (uri, languageId, version, text) ------>|
     |--- textDocument/didChange ------------------------------------->|
     |<-- textDocument/publishDiagnostics ----------------------------|
     |--- textDocument/completion ------------------------------------>|
     |<-- CompletionList ----------------------------------------------|
     |--- textDocument/didClose -------------------------------------->|
     |--- shutdown (request) ----------------------------------------->|
     |<-- null --------------------------------------------------------|
     |--- exit (notification) ---------------------------------------->|
     |                                  server exits with code 0

The rules are strict. initialize is the first request and is sent once; any other request before it gets error -32002 (ServerNotInitialized), and early notifications are dropped except exit. Until the server answers, the client sends nothing else, and the server may send only window/showMessage, window/logMessage, telemetry/event, window/showMessageRequest and progress on a token the client supplied. initialized follows once. shutdown asks the server to stop work but not exit; afterwards the client may send only exit, and the server exits with code 0 if shutdown came first, 1 otherwise.

The initialize parameters tell the server where the project is: processId, clientInfo, locale, rootPath and rootUri (both deprecated), workspaceFolders, free form initializationOptions, the client's capabilities and a trace level (off, messages or verbose).

Capabilities and position encodings

Capability flags in initialize say what each side supports: the client declares, for example, dynamic registration support and position encodings; the server answers with providers such as definitionProvider, completionProvider or executeCommandProvider with its command names. Since 3.0, client/registerCapability and client/unregisterCapability let servers change capabilities after start up. The spec says features are kept compatible "using so called capability flags", so a 3.18 client and a 3.16 server negotiate down to what both understand.

Positions are a zero based line and character offset. Before 3.17 offsets always counted UTF-16 code units. Since 3.17 the client lists encodings in general.positionEncodings and the server picks one; UTF-16 remains mandatory and the default.

Cancellation, progress and error codes

$/cancelRequest carries the ID to cancel, but the server must still respond, because JSON-RPC requires a response to every request. Since 3.15, $/progress reports work done and partial results.

CodeNameSource
-32700, -32600, -32601, -32602, -32603Parse error, invalid request, method not found, invalid params, internal errorJSON-RPC
-32002ServerNotInitializedLSP
-32803RequestFailed (since 3.17)LSP
-32802ServerCancelled (since 3.17)LSP
-32801ContentModifiedLSP
-32800RequestCancelledLSP

The main requests and notifications

didOpen, didChange, didClose

Client support for these three notifications is mandatory, and a server implements all three or none. textDocument/didOpen carries the URI, language ID, version and full text; from then on "the server must not try to read the document's content using the document's Uri", because the editor's buffer is the truth. didChange sends full text or incremental edits, depending on the sync kind the server chose. didClose hands ownership back to the file system.

completion and definition

textDocument/completion returns items for a cursor position. The client usually filters and sorts as the user types; expensive details can wait for completionItem/resolve. textDocument/definition sends a URI and position and gets back locations, with declaration, typeDefinition, implementation and references following the same pattern.

Diagnostics: push and pull

In the push model the server sends textDocument/publishDiagnostics when it chooses. New diagnostics replace old ones for that document, and the server clears them by pushing an empty array. Version 3.17 added the pull model, textDocument/diagnostic and workspace/diagnostic, so the client can ask for the file being edited first.

codeAction, executeCommand and applyEdit

textDocument/codeAction returns fixes and refactorings for a range, as edits, commands or both. The spec recommends commands be handled by the server. That creates the most powerful pair in the protocol: workspace/executeCommand (client to server) runs a named command, and workspace/applyEdit (server to client) asks the client to change files. In the spec's words, "the server creates a WorkspaceEdit structure and applies the changes to the workspace using the request workspace/applyEdit." Workspace edits can create, rename and delete files.

Requests from server to client

MethodWhat it asks the client to do
client/registerCapabilityAdd a capability after start up
workspace/configurationReturn configuration values the server names (since 3.6)
workspace/applyEditApply text and file edits
window/showMessageRequestShow a message with action buttons
window/showDocumentOpen a URI, optionally "in an external program" (since 3.16)
workspace/diagnostic/refreshRe-pull diagnostics
telemetry/event (notification)Log a telemetry event

Each row is a place where the client acts on the server's say so.

Versions and history

The DAP overview dates LSP's introduction to 2016. The change log writes dates month first (12/14/2020):

VersionDateHighlights
2.xBefore 3.0Original public version; 3.1 notes describe breaking changes from 2.x
3.0Not dated in the logCapability flags, experimental capabilities, dynamic registration, willSaveWaitUntil, rootUri
3.1 to 3.402/28/2017 to 11/27/2017Commit characters, CompletionContext, MarkupContent
3.5SkippedAligned with the vscode-languageserver-protocol npm module version
3.602/22/2018Workspace folders, workspace/configuration, type definition, implementation
3.7 to 3.1404/05/2018 to 12/13/2018Code action literals, folding ranges, prepare rename, file operations in edits, declarations
3.1501/14/2020Generic progress, selection ranges, client and server info
3.1612/14/2020Semantic tokens, call hierarchy, tracing, window/showDocument, file operation events
3.1705/10/2022Pull diagnostics, inlay hints, type hierarchy, notebooks, position encoding negotiation, meta model
3.1806/04/2026Inline completions, dynamic text document content, multi range formatting, snippets in workspace edits

3.18 status and what it adds

The site's home page says "The latest version of the LSP specification is version 3.18", and the 3.17 page calls itself "the previous 3.17.x version". The 3.18 change log lists fourteen items, including textDocument/inlineCompletion, workspace/textDocumentContent for virtual documents (clients "should treat the content returned from this requests as readonly"), snippets and metadata in workspace edits, relative patterns in document filters, and a debug message kind. Adoption is gradual because every feature sits behind a capability.

The separate Base Protocol 0.9 splits the shared plumbing out of LSP, motivated by the Build Server Protocol, whose messages "match almost exactly with their LSP counterparts". The page says it is "under experimental construction and subject to breaking changes".

Security properties and known weakness classes

The LSP specification has no Security Considerations section. What follows comes from the protocol's design, the JSON-RPC specification and the editors' own security documentation, described as concepts.

What the protocol does and does not guarantee

PropertyIn LSP?What actually provides it
ConfidentialityNoLocal pipes and process boundaries
IntegrityNo; byte counted framing onlyThe operating system's pipe semantics
AuthenticationNoThe client chose which binary to start
AuthorizationNoEditor policy on servers, extensions and settings
AvailabilityPartial: cancellation, ContentModified, restart guidanceClient crash and timeout handling

LSP assumes the client launched a process it trusts over a private channel. That holds for stdio. It is weaker for the socket option: anything that can reach the socket can speak the protocol, so keep listeners on loopback or behind a socket file with tight permissions. The bigger point is that the protocol is not where most risk lives. It lives in what the server may do, and what the client does when the server asks.

Language servers run project code

A server has to understand the whole project. Depending on the language, that can mean evaluating build files, resolving dependencies, running code generation, expanding compile time macros or loading plugins the project declares. Some of that is code from the repository you just cloned.

VS Code's Workspace Trust extension guide names this: "a language extension, in order to provide functionality, may execute code from the currently loaded workspace." The user guide adds that "an unethical person could craft an innocent looking project that would run malicious code without your knowledge." Nothing in LSP limits this; a server is an ordinary process with your permissions.

Workspace settings that point to executables

Servers and the extensions that launch them read settings naming a compiler, linter or interpreter. If a repository's own configuration can set those, the repository chooses what your editor runs. VS Code warns that some workspace settings "contain paths to executables (for example, linter binaries), which if set to point to malicious code, could cause damage", and its built in PHP extension limits php.validate.executablePath to trusted folders. Many servers fetch such values through workspace/configuration, so the weakness flows through the protocol even though the protocol is neutral.

Untrusted repositories and project local configuration

Neovim's exrc option, off by default, makes the editor execute .nvim.lua, .nvimrc or .exrc files from the current and parent directories, but only if they are on a trust list managed with :trust. Its documentation says project local LSP configuration is done through exrc, so a repository's LSP setup is code, not data. The weakness class: opening a folder is treated as reading, but it can cause execution. Workspace Trust and the exrc trust list both restore that line.

Extensions that bundle servers

Language servers often arrive inside an extension. VS Code's extension runtime security page says the extension host "has the same permissions as VS Code itself" and an extension can "read and write files on your machine, make network requests, run external processes, and modify workspace settings." Workspace Trust "can't prevent a malicious extension from executing code and ignoring Restricted Mode." Since release 1.97, VS Code asks you to trust a third party publisher on first install, though installs from the command line do not trust the publisher automatically. See VS Code extension marketplace security and VS Code extension supply chain risk in 2026.

Server to client authority

A server can ask the client to change, create, rename or delete files, to open a URI in an external program, and to show prompts. A compromised server can ask at the wrong time. The weakness class is over trusting the peer; the client is the enforcement point, and good clients tie edits to user actions, restrict external opens to safe schemes, and log requests.

Framing, encoding and desynchronisation

  • Stray output. With stdio, anything else a server prints to standard output corrupts the stream. Claude Code runs every language server over stdio, "so the stdout protocol rules apply to all servers."
  • Bytes versus characters. Content-Length counts bytes; positions count UTF-16 code units unless negotiated. Mixing them truncates messages or misplaces edits.
  • Unbounded sizes. The base protocol sets no maximum message size, so a parser that trusts a huge length invites resource exhaustion.

These are implementation bugs, which is why they appear as CVEs in LSP client and server libraries.

Availability and information exposure

The spec tells clients to restart a crashed server carefully: "VS Code, for example, doesn't restart a server which has crashed 5 times in the last 180 seconds." Since every request needs a response, a server that never answers can stall a client without timeouts. On exposure, didOpen and didChange carry source text, so verbose traces left on and shipped to a log collector quietly leak code.

AI coding tools: diagnostics as model input

Claude Code's code intelligence plugins push a server's diagnostics into Claude's context after each edit and add a read only LSP tool for definitions, references, symbols and call hierarchies; its documentation says these servers are not started in cloud sessions. The weakness class is old: untrusted text becomes model input. Diagnostic messages, hover text and symbol names come from repository content. VS Code's Workspace Trust page says "Any file could be pulled into the context by using agents and could theoretically result in a prompt injection attack", and Restricted Mode disables agents. See what prompt injection is and our AI coding assistant security checklist.

Secure configuration and defender side checks

Everything here comes from the editors' official documentation, listed under Sources. Only run these checks on machines and repositories you own or are authorised to test.

VS Code: keep Workspace Trust on and tighten its defaults

Workspace Trust arrived in VS Code 1.57. The documented settings:

SettingDefaultSafer choice
security.workspace.trust.enabledtrueLeave on
security.workspace.trust.startupPromptnever (Restricted Mode with a banner)never or once
security.workspace.trust.emptyWindowtruefalse, so empty windows start restricted
security.workspace.trust.untrustedFilespromptnewWindow for loose files
extensions.supportUntrustedWorkspacesno overridesOverride per extension and version, with care
{
  "security.workspace.trust.enabled": true,
  "security.workspace.trust.emptyWindow": false,
  "security.workspace.trust.untrustedFiles": "newWindow"
}

Restricted Mode disables or limits AI agents, the terminal, tasks, debugging, settings tagged requireTrustedWorkspace, and extensions that have not opted in. The documentation suggests cloning unfamiliar code into a separate parent folder (its example is ForEvaluation) and trusting only the parent folder that holds your own repositories, since trusting a parent trusts every subfolder.

Extensions declare their stance in package.json through the untrustedWorkspaces capability (supported set to true, false or limited), and can list restrictedConfigurations so workspace values for risky settings are ignored until trust is granted. When you review an extension that launches a language server, that declaration shows whether its author thinks the server is safe in an untrusted folder. Do not use the command line switch that disables Workspace Trust.

VS Code: inventory extensions and processes

From the VS Code command line documentation:

# List installed extensions with their versions
code --list-extensions --show-versions

# Print process usage and diagnostics information
code --status

# Open a session with every extension disabled, to review a repository
code --disable-extensions

Language servers launched by extensions run as separate processes, so review the status output for any process you do not recognise. In the Workspace Trust editor, a link opens the Extensions view filtered with @workspaceUnsupported, listing extensions disabled or limited in Restricted Mode.

For evidence of what a server asks the editor to do, the language server extension guide documents a per client trace setting for vscode-languageclient, named after the client ID; its sample sets languageServerExample.trace.server to verbose and shows traffic in the Output panel. Turn it off afterwards, because it captures your code.

Neovim: see and restrict language servers

From Neovim's LSP documentation:

" Show enabled LSP configurations and client status
:checkhealth vim.lsp

" Show what the first attached server says it can do
:lua =vim.lsp.get_clients()[1].server_capabilities

" Stop clients for this buffer, or disable a config
:lsp stop
:lsp disable

A config enabled with vim.lsp.enable() attaches only when the filetype matches and a workspace root is found through root_markers or root_dir(), unless it sets workspace_required=false. Keep exrc off unless you need it, and read files before adding them with :trust.

Claude Code: code intelligence plugins

Claude Code configures servers through a plugin's .lsp.json or lspServers entry. The plugins reference example:

{
  "lspServers": {
    "go": {
      "command": "gopls",
      "args": ["serve"],
      "extensionToLanguage": { ".go": "go" }
    }
  }
}

Defender relevant fields: requestTimeout (default 60,000 ms), restartOnCrash (default true), maxRestarts, shutdownTimeout, and diagnostics (default true; false stops pushing diagnostics into context). The code intelligence page says a plugin "doesn't include the language server", so you install the binary yourself and can confirm which one will run:

which typescript-language-server

In a session, /plugin has an Errors tab naming any binary it could not find. See also Claude Code security posture and MCP vs skills vs hooks vs rules.

Supply chain hygiene for the servers

Language servers are installed software: global npm packages, Go and Rust binaries, Python packages, or files inside an extension. Pin versions in dev containers and setup scripts (see our dev container security baseline), install from the language project's own release channel, keep a team inventory of extensions, and track CVEs in JSON-RPC and LSP libraries like any other dependency.

When to use LSP versus DAP, MCP and the alternatives

How the three protocols relate in AI coding tools

The DAP overview says its approach "was already introduced in 2016 with the Language Server Protocol"; the MCP specification says MCP "takes some inspiration from the Language Server Protocol".

LSPDAPMCP
ConnectsEditor or agent to a language serverEditor to a debug adapter for a debugger or runtimeAI host to tools, resources and prompts
MessagesJSON-RPC 2.0 in Content-Length framesIts own JSON messages (seq, request, response, event) in Content-Length framesJSON-RPC 2.0
Transportstdio, pipes, sockets, node IPCstdio per session, or a port in multi session modestdio or Streamable HTTP
AuthenticationNoneNoneOAuth 2.1 based authorization for HTTP
Peer can make youApply edits, open URIsRun a program; runInTerminal asks the client to run a commandCall tools, request input

A coding agent reads code through LSP, runs and inspects programs through DAP or a terminal, and reaches outside systems through MCP. Each widens what a repository can influence, which is why Restricted Mode turns off debugging, tasks and agents together. For MCP in depth, see Model Context Protocol explained; for agent delegation, the A2A explainer.

Decision table

Use caseBest fitSecurity propertiesOperational cost
Completion, navigation and diagnostics in several editorsLSPNo auth or encryption; relies on a trusted local process and editor trustLow to moderate: one server per language
Breakpoints, stepping, variable inspectionDAPNo auth; debugging launches code, so editors gate it behind trustModerate: one adapter per debugger
AI access to tickets, databases or APIsMCPOAuth 2.1 for remote servers; host mediated consentModerate: run and version a server
Code navigation without a local checkoutLSIF indexStatic data, no live process reading the repositoryLow at read time; indexing in CI
A feature for one editor onlyThat editor's plugin APIThe editor's own process permissionsLow once; repeated per editor

Rule of thumb: understanding code in an editor is LSP; running a program is DAP; a model choosing an outside capability is MCP. Whatever you pick, the trust decision belongs to the folder, not the protocol.

Where Safeguard fits

Safeguard does not sit in the LSP channel and does not replace Workspace Trust or extension controls. It covers the software around the protocol:

  • Software composition analysis with full transitive inventory (generally available) covers language servers and JSON-RPC or LSP libraries declared as project or tooling dependencies.
  • SBOM generation and analysis in SPDX and CycloneDX and container visibility and scanning (both generally available) record which server versions ship in dev container and CI images.
  • Vulnerability management with EPSS and KEV enrichment and reachability analysis (generally available) rank those findings, and policies and gates (generally available) stop known vulnerable versions returning.
  • For AI coding tools, the Safeguard MCP server at mcp.safeguard.sh and agent identity are generally available. MCP Guardrail and the AI Gateway are in early access.

Safeguard does not inspect, filter or block LSP or DAP traffic.

Common mistakes

  • "I only opened it." A server may build the project to answer your first hover.
  • Disabling Workspace Trust instead of using a trusted parent folder.
  • Overriding extensions.supportUntrustedWorkspaces broadly. The author may have "a good reason for disabling their extension in Restricted Mode".
  • Assuming Restricted Mode stops a malicious extension. It does not.
  • Committing workspace settings that point to binaries.
  • Binding a socket transport to all interfaces. LSP has no authentication.
  • Printing debug output to stdout in a stdio server.
  • Confusing byte lengths with character offsets.
  • Leaving verbose traces on where logs are shared.
  • Treating diagnostics as trusted when an agent reads them.
  • Unpinned server installs, so nobody can answer "are we affected?".

FAQ

What is the Language Server Protocol in simple terms?

A standard way for an editor to ask a separate program, the language server, for completion, go to definition and error checking. One server works in every editor that speaks LSP.

What port does LSP use?

None by default. Most servers use stdio; the socket option takes whatever port the client passes.

Is LSP encrypted?

No. It has no encryption or authentication and relies on a private local channel to a process the editor started.

Does LSP use JSON-RPC?

Yes. Every message is a JSON-RPC 2.0 request, response or notification behind a Content-Length header. Batches are not supported.

What is the latest LSP version?

On 23 October 2026 the official site lists 3.18 (change log date 06/04/2026). 3.17 (05/10/2022) is the previous version.

Can a language server run code from my project?

Yes. VS Code's extension guide says a language extension "may execute code from the currently loaded workspace", and workspace settings can point tools at executables.

Is it safe to open an untrusted repository in VS Code?

Open it in Restricted Mode and review it first. Restricted Mode limits extensions, tasks, debugging, the terminal and agents, but not an extension that is itself malicious.

LSP vs DAP: what is the difference?

LSP gives editors code intelligence; DAP connects editors to debuggers. Both use Content-Length framing, but DAP uses its own JSON messages rather than JSON-RPC.

LSP vs MCP: what is the difference?

LSP connects a development tool to a language server. MCP connects an AI application to tools and data, with OAuth based authorization for remote servers. MCP's specification credits LSP as inspiration.

How do I see which language servers my editor runs?

VS Code: list extensions with versions and review the status output. Neovim: :checkhealth vim.lsp. Claude Code: /plugin and its Errors tab.

Fix it without waiting for upstream

LSP security bugs rarely live in the specification. They live in a language server, an editor extension, or the JSON-RPC and LSP libraries underneath, which makes most of them ordinary dependency problems.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show dependency remediation, local scanning and agent controls, not an LSP traffic filter. Download the CLI from https://safeguard.sh/download and read the setup guide at https://docs.safeguard.sh/docs/cli .

The first demo is a real Safeguard CLI session. Asked to fix the lodash CVE in a repo, it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing, then shows /permissions, /model and /scan. The same approval gated flow applies when a language server package or JSON-RPC library in your tooling needs an upgrade.

Click through it at your own pace, or open it full screen.

The second demo sets up the local runner on your own machine, approves it with its device code, then scans from the terminal. Scanning where your editor tooling and dev containers live keeps language server versions in the same inventory as your other dependencies.

Click through it at your own pace, or open it full screen.

The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude can use. If your coding agent already reads code through LSP, this adds vulnerability context through MCP while keeping the tool list deliberately small.

Click through it at your own pace, or open it full screen.

Quick check

A language server receives a textDocument/completion request before any initialize request. What should it do?

Answer with error code -32002 (ServerNotInitialized). The specification makes initialize the first request; early notifications are dropped, except exit, so a server can still be told to quit.

You open an unfamiliar repository in VS Code with Workspace Trust on. What runs, and what does not?

The folder opens in Restricted Mode. You can browse and edit, but extensions that have not opted in are disabled or limited, trust gated workspace settings are ignored, and tasks, debugging, the terminal and AI agents are blocked until you trust the folder. An already installed malicious extension is not stopped.

Sources

Last reviewed: 23 October 2026. LSP versions and editor trust settings change; check the official specification and your editor's documentation for the current state.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.