vulkro lsp
Run Vulkro as a Language Server Protocol server. Any editor with a
generic LSP client can stream Vulkro findings into the gutter on
didOpen and didSave: Neovim (with nvim-lspconfig), Helix
(languages.toml), Emacs (eglot), Zed, Sublime LSP. Thin client
extensions for VS Code (also Cursor / Windsurf) and JetBrains IDEs live
in editor-extensions/ and are clients over this same server.
The subcommand IS the server: there is no vulkro lsp serve. Pass
nothing and stdin / stdout becomes a JSON-RPC 2.0 channel framed by
the standard LSP Content-Length headers.
Usage
vulkro lsp
The server reads Content-Length-framed JSON-RPC messages from
stdin and writes responses + notifications back to stdout. Stderr
is reserved for log lines (set VULKRO_LSP_LOG=1 to enable them;
off by default so the JSON-RPC channel stays free of side-band
output).
Methods supported
| Method | Direction | Notes |
|---|---|---|
initialize / initialized | client to server | Standard handshake. Server advertises textDocumentSync, executeCommandProvider (vulkro.explain), hoverProvider, codeActionProvider, and diagnosticProvider. |
textDocument/didOpen | client to server | First open in a workspace triggers a full project scan; subsequent opens reuse the cached result. |
textDocument/didChange | client to server | Accepted but does not trigger a scan. Vulkro reads from disk; re-scanning a stale buffer would mis-report. |
textDocument/didSave | client to server | Re-analyses only the saved file and merges it into the cached project result. Falls back to a whole-project rescan when the saved file takes part in a cross-file finding or nothing is cached yet. saveScope: project (or VULKRO_LSP_INCREMENTAL=0) rescans the whole project on every save. |
textDocument/didClose | client to server | Clears diagnostics for the closed file. |
textDocument/hover | client to server | Markdown bubble for a Vulkro diagnostic under the cursor: rule id, severity, message, remediation. |
textDocument/codeAction | client to server | Two quick-fixes per diagnostic: Suppress (inserts a vulkro:disable-next-line comment) and Explain (runs vulkro.explain). Leaves other servers' diagnostics untouched. |
textDocument/publishDiagnostics | server to client | Pushed per file; non-empty when Vulkro found something. |
workspace/executeCommand | client to server | One command registered: vulkro.explain with a rule-id string returns markdown. |
shutdown + exit | client to server | Standard LSP shutdown sequence. Exit 0 when followed correctly. |
Diagnostic shape
Each Diagnostic payload carries:
range: 0-indexedstart.linematching the finding's source line, widened to the full row (character 0 to end of line) because Vulkro detectors don't all populate column data.severity: LSPDiagnosticSeverity(1 Error, 2 Warning, 3 Information, 4 Hint). VulkroCriticalandHighmap to Error;Mediumto Warning;Lowto Information;Infoto Hint.code: the stable Vulkro finding id (UUID per emit site for the current release; the website rule-page URLs are derived from the OWASP-category slug carried alongside).source: the literal stringvulkro.message: the human-readable finding message, with a trailingRemediation: ...line when the finding carries one.
Flags
There are no flags in this release. Transport is stdio only; an SSE
transport (analogous to vulkro mcp serve --port) can be added in a
follow-up if a client requests it.
Exit codes
| Code | Meaning |
|---|---|
0 | Clean shutdown: the client closed stdin, or sent LSP exit after a prior shutdown. |
2 | Operational error: transport-level IO failure, malformed framing, bad argument to lsp. |
Environment variables
| Variable | Effect |
|---|---|
VULKRO_LSP_LOG=1 | Emit per-message trace lines on stderr. Off by default so the stdio JSON-RPC channel stays clean. |
VULKRO_LSP_DEBOUNCE_MS=<N> | Override the 300 ms didChange republish debounce window (clamped to [50, 5000]). |
VULKRO_LSP_INCREMENTAL=0 | Rescan the whole project on every save instead of re-analysing only the saved file (0, false, no or off). |
VULKRO_OFFLINE=1 | Inherited from scan: disables every outbound HTTP call. |
Every other VULKRO_* var | Inherited from scan. Anything that changes scan behaviour (cache disable, blame, etc.) flows through transparently. |
Editor configuration
Neovim (nvim-lspconfig)
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
if not configs.vulkro then
configs.vulkro = {
default_config = {
cmd = { 'vulkro', 'lsp' },
filetypes = { 'python', 'javascript', 'typescript', 'go' },
root_dir = lspconfig.util.root_pattern(
'.git', 'package.json', 'pyproject.toml', 'go.mod',
'Cargo.toml'),
settings = {},
},
}
end
lspconfig.vulkro.setup({})
Vulkro is a security overlay, not a typechecker, so you almost
certainly want it running alongside your primary language server
(pyright / tsserver / gopls / etc.). Multiple LSPs per buffer is
the default nvim-lspconfig behaviour.
Helix (~/.config/helix/languages.toml)
[language-server.vulkro]
command = "vulkro"
args = ["lsp"]
[[language]]
name = "python"
language-servers = ["pyright", "vulkro"]
[[language]]
name = "javascript"
language-servers = ["typescript-language-server", "vulkro"]
[[language]]
name = "typescript"
language-servers = ["typescript-language-server", "vulkro"]
[[language]]
name = "go"
language-servers = ["gopls", "vulkro"]
Emacs eglot (init.el)
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs
'((python-mode python-ts-mode) . ("vulkro" "lsp")))
(add-to-list 'eglot-server-programs
'((js-mode js-ts-mode typescript-mode typescript-ts-mode)
. ("vulkro" "lsp")))
(add-to-list 'eglot-server-programs
'((go-mode go-ts-mode) . ("vulkro" "lsp"))))
eglot will spawn Vulkro alongside the language-default server for each filetype.
Zed
Add to your settings JSON (Cmd-, then "edit settings"):
{
"lsp": {
"vulkro": {
"binary": { "path": "vulkro", "arguments": ["lsp"] }
}
}
}
VS Code, Cursor, Windsurf
The dedicated Vulkro extension is a thin client over vulkro lsp:
diagnostics in the gutter and Problems pane, hover bubbles, and
Suppress / Explain quick-fixes. Cursor and Windsurf are VS Code forks
and use the same extension. The extension requires the vulkro binary
on PATH (or set vulkro.lsp.path). See the
VS Code integration guide for setup.
JetBrains IDEs (IntelliJ, PyCharm, WebStorm, GoLand, Rider, RubyMine)
On any 2023.2+ JetBrains IDE you can register vulkro lsp as a generic
LSP server through the platform LSP4IJ client, so diagnostics stream into
the editor with no extra binary. Point the server command at vulkro lsp.
Performance
The first didOpen in a workspace triggers a full project scan,
which can take seconds on a large codebase (Vulkro is exhaustive
by default). Subsequent didOpen events in the same project reuse
the cached ScanResult and publish diagnostics in milliseconds.
didSave re-analyses only the saved file and merges it into the
cached project result, so a save costs a single-file scan rather than
a whole-project one. When the saved file takes part in a cross-file
finding (which a single-file analysis cannot rebuild), or nothing is
cached yet, the save falls back to the whole-project rescan, so
cross-file findings are never dropped. The vulkro.rescan command runs
a whole-project scan on demand.
To rescan the whole project on every save instead, pass
initializationOptions.saveScope: "project" or set
VULKRO_LSP_INCREMENTAL=0.
Read-only by design
The LSP never auto-writes your project: no willSaveWaitUntil handler, no
formatter, no edit applied without your action. The one code action that
proposes an edit is Suppress, which inserts a vulkro:disable-next-line
comment, and only when you click it; Vulkro never rewrites your logic. It also
makes no outbound network call of its own (VULKRO_OFFLINE=1 is honoured
regardless). The brand promise (Vulkro reads, you write) holds on the LSP
surface the same way it holds on the CLI.
What's not yet supported
- Mutating autofix code actions (wiring
vulkro fixrewrites asCodeActionedits). Today's code actions are Suppress + Explain only. - True per-file incremental scanning beyond the existing per-project cache.
- Marketplace-published extension packages (the VS Code and JetBrains sources
live in
editor-extensions/; publishing is a follow-up).