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. Dedicated
extensions for VS Code, Cursor, and JetBrains IDEs ship in a
follow-up release; this is the foundation they share.
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 → server | Standard handshake. Server advertises textDocumentSync, executeCommandProvider (vulkro.explain), and diagnosticProvider. |
textDocument/didOpen | client → server | First open in a workspace triggers a full project scan; subsequent opens reuse the cached result. |
textDocument/didChange | client → server | Accepted but does not trigger a scan. Vulkro reads from disk; re-scanning a stale buffer would mis-report. |
textDocument/didSave | client → server | Re-runs the project scan and republishes diagnostics for every open file in the project. |
textDocument/didClose | client → server | Clears diagnostics for the closed file. |
textDocument/publishDiagnostics | server → client | Pushed per file; non-empty when Vulkro found something. |
workspace/executeCommand | client → server | One command registered: vulkro.explain with a rule-id string returns markdown. |
shutdown + exit | client → 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 → u32::MAX) 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_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',
'sfdx-project.json', 'pom.xml', 'build.gradle',
'composer.json', '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, JetBrains
Dedicated extensions ship in a follow-up release. Until then, the
VS Code generic LSP support and the JetBrains LSP support (2023.2+)
can both spawn vulkro lsp directly.
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-runs the scan; for a single-file change this is
typically under a second on a small project.
True incremental scanning (re-scan only the changed file) ships in a follow-up release. The current model is acceptable for small-to-medium projects because the scanner already parallelises across cores; we'll revisit when feedback shows it isn't.
Read-only by design
Vulkro never writes to your project from the LSP. There is no
quick-fix code action that mutates files, no willSaveWaitUntil
handler, no formatter. 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
- Quick-fix code actions (planned: a follow-up wiring of
vulkro fixrewrites asCodeActionentries). - Inline suppression UI (
// vulkro:disable[<rule-id>]markers already work in the scanner; surfacing them as a one-click LSP action lands later). - True per-file incremental scanning beyond the existing per-project cache.
- VS Code, Cursor, and JetBrains extension packages: shipped as follow-up releases that wrap this binary.