Safeguard
Product

How the Snyk Language Server powers IDE plugins across VS...

A technical look at how Snyk's Go-based Language Server uses LSP and a delegating scanner pattern to power VS Code, JetBrains, and Eclipse plugins from one binary.

Aman Khan
AppSec Engineer
7 min read

How the Snyk Language Server Powers IDE Plugins Across VS Code, JetBrains, and Eclipse

For years, Snyk maintained separate codebases for its VS Code extension, JetBrains plugin, and Eclipse plugin — each reimplementing scanning logic, authentication, and result rendering on its own schedule. A fix shipped to one editor could take months to reach another, and feature parity across IDEs was more aspiration than reality. Snyk's answer was to stop building IDE integrations one at a time and instead build a single background process — the Snyk Language Server (snyk-ls) — that speaks a standard protocol every modern editor already understands. Written in Go and distributed as a self-contained binary, it now does the scanning, caching, and issue-formatting work for all three editors, while each plugin's job shrinks to rendering results in that IDE's native UI. This piece walks through how that architecture is documented publicly, without editorializing on how it compares to any other vendor's approach.

What problem was the language server actually built to solve?

The core problem was fragmentation, not a missing feature. Before snyk-ls existed, Snyk shipped distinct scanning logic inside each IDE plugin, which meant three (or more) codebases had to independently implement dependency parsing, vulnerability lookups, and diagnostic rendering, and keep them all current with the latest CLI and API behavior. Snyk's own engineering blog on the move, published September 6, 2022 to coincide with Eclipse Plugin 2.0, framed it plainly: developers using editors like Vim, Emacs, Sublime Text, or Eclipse had no first-class integration and were pushed toward the command-line tool instead. Rather than write and maintain a fourth or fifth bespoke plugin, Snyk adopted the Language Server Protocol (LSP) — a specification Microsoft open-sourced in 2016 specifically so tools could implement diagnostics, quick fixes, and code intelligence once and have any LSP-compliant editor consume them for free.

How does one background process talk to three completely different IDEs?

It talks to all of them through the same JSON-RPC wire format, because LSP standardizes the transport, not the editor. snyk-ls runs as a long-lived process that communicates over stdio using JSON-RPC (the project's public source references the jrpc2 library for this), exchanging standard LSP messages like initialize, initialized, and textDocument/publishDiagnostics. VS Code, JetBrains IDEs (via the LSP4IJ or equivalent client layer), and Eclipse (via LSP4E) each already ship generic LSP client machinery for talking to language servers — originally built for things like autocomplete and go-to-definition. Snyk's plugins reuse that same client plumbing and layer on Snyk-specific JSON-RPC extensions, including custom notifications such as $/snyk.scan, $/snyk.configuration, and $/snyk.treeView, which carry information standard LSP has no vocabulary for, like scan progress or Snyk's issue tree view. The practical effect: the protocol layer between editor and scanner is identical everywhere, and only the thin client-side rendering code differs per IDE.

How does a single binary cover four different kinds of security scanning?

It doesn't do all four itself — it delegates. According to the project's documented architecture, snyk-ls is organized around a central DelegatingScanner that coordinates four independent, product-specific scanners: one for Snyk Code (static analysis), one for Snyk Open Source (dependency vulnerabilities), one for Infrastructure as Code (misconfigurations in Terraform, Kubernetes, CloudFormation, and Azure Resource Manager templates), and one for Secrets detection. Each scanner implements a common interface so the delegating layer can trigger and coordinate them uniformly across a workspace folder, without the core server needing product-specific logic baked into it. The codebase reportedly follows a domain-driven design split into infrastructure (the individual scanners and external integrations), domain (workspace and scan coordination logic), and application (server orchestration and configuration) layers — a structure aimed at letting Snyk add or change a scan type without touching the LSP transport code that all four share.

Where does the actual vulnerability and misconfiguration data come from?

It comes from two different backends depending on the scan type, not from logic embedded in the language server itself. Per Snyk's own documentation, Open Source and Infrastructure as Code scanning use the Snyk CLI as the data provider, while Secrets scanning also uses the CLI where applicable — meaning snyk-ls shells out to (or is bundled with) the same snyk binary that runs in CI pipelines and terminals. Snyk Code, by contrast, calls the Snyk Code API directly rather than going through the CLI. That split matters practically: since the CLI now bundles the language server itself, installing or updating one typically updates the other together, and the CLI is auto-downloaded the first time an IDE plugin needs it, cached locally (documentation points to XDG_DATA_HOME on the relevant platforms). The language server ships prebuilt for a fairly wide binary matrix — Linux (amd64, arm64), Linux Alpine (386, amd64), Windows (386, amd64), and macOS (amd64, arm64) — which is what lets three unrelated Java-based, Electron-based, and Eclipse-based clients all launch "the same" process on a developer's machine regardless of OS.

How do plugin updates stay in sync with a server they don't control the release cycle of?

They stay in sync because the protocol version is embedded directly in the download link, not negotiated loosely at runtime. Snyk's own contribution documentation is explicit that a protocol change must always ship alongside a corresponding plugin update — if the language server's protocol version is bumped without updating a client like the Eclipse plugin, that plugin simply stops receiving further language server updates until it's upgraded. This is a deliberate compatibility gate: each IDE plugin requests a language server build matching a known protocol version rather than always grabbing "latest," which avoids a scenario where an old JetBrains plugin suddenly gets fed diagnostic payloads in a new format it can't parse. It also means Snyk can iterate the server (adding new custom $/snyk.* messages or changing initialization options) without silently breaking every IDE integration on the next binary rollout.

What does a scan actually look like from a developer's chair?

It looks like automatic, editor-native feedback rather than a manual CLI invocation. Scans are documented as triggering on IDE startup, on file open, and on file save, with results delivered as standard LSP diagnostics — the same mechanism editors already use to underline a syntax error — so a dependency vulnerability or an IaC misconfiguration shows up inline with a severity marker, without the developer running snyk test by hand. Configuration (which products are active, authentication method, API endpoint, organization, severity thresholds) is passed once at LSP initialization via initializationOptions, using the CLI's own flag names as keys, and the server exposes code actions for remediation, including AI-assisted autofix suggestions surfaced as standard LSP code actions. Because this pipeline is identical underneath VS Code, JetBrains IDEs, and Eclipse, a scanning behavior change Snyk ships once should, in principle, show up the same way in all three editors on their next language server update — that consistency is the entire point of routing everything through one LSP server instead of three separate plugin codebases.

How Safeguard Helps

Snyk's language-server architecture is a useful case study precisely because it's a pattern security teams increasingly rely on: a single, auto-updating background binary with broad filesystem and network access, quietly shelling out to a CLI and calling external APIs from inside a developer's IDE. That's exactly the kind of dependency Safeguard is built to give visibility into. Safeguard helps engineering and security teams inventory the third-party tools, IDE extensions, and auto-downloaded binaries running across their developer fleet, verify what those components are actually calling out to and what data they access, and flag when an auto-updating agent changes behavior or scope between versions. For organizations standardizing on developer tooling like language-server-based scanners, Safeguard provides the software supply chain visibility to confirm those tools are running the versions you expect, communicating with the endpoints you've approved, and not silently expanding their footprint as they update — without requiring you to trust vendor documentation alone.

Never miss an update

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