Skip to main content

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

MethodDirectionNotes
initialize / initializedclient → serverStandard handshake. Server advertises textDocumentSync, executeCommandProvider (vulkro.explain), hoverProvider, codeActionProvider, and diagnosticProvider.
textDocument/didOpenclient → serverFirst open in a workspace triggers a full project scan; subsequent opens reuse the cached result.
textDocument/didChangeclient → serverAccepted but does not trigger a scan. Vulkro reads from disk; re-scanning a stale buffer would mis-report.
textDocument/didSaveclient → serverRe-runs the project scan and republishes diagnostics for every open file in the project.
textDocument/didCloseclient → serverClears diagnostics for the closed file.
textDocument/hoverclient → serverMarkdown bubble for a Vulkro diagnostic under the cursor: rule id, severity, message, remediation.
textDocument/codeActionclient → serverTwo quick-fixes per diagnostic: Suppress (inserts a vulkro:disable-next-line comment) and Explain (runs vulkro.explain). Leaves other servers' diagnostics untouched.
textDocument/publishDiagnosticsserver → clientPushed per file; non-empty when Vulkro found something.
workspace/executeCommandclient → serverOne command registered: vulkro.explain with a rule-id string returns markdown.
shutdown + exitclient → serverStandard LSP shutdown sequence. Exit 0 when followed correctly.

Diagnostic shape

Each Diagnostic payload carries:

  • range: 0-indexed start.line matching the finding's source line, widened to the full row (character: 0 → u32::MAX) because Vulkro detectors don't all populate column data.
  • severity: LSP DiagnosticSeverity (1 Error, 2 Warning, 3 Information, 4 Hint). Vulkro Critical and High map to Error; Medium to Warning; Low to Information; Info to 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 string vulkro.
  • message: the human-readable finding message, with a trailing Remediation: ... 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

CodeMeaning
0Clean shutdown: the client closed stdin, or sent LSP exit after a prior shutdown.
2Operational error: transport-level IO failure, malformed framing, bad argument to lsp.

Environment variables

VariableEffect
VULKRO_LSP_LOG=1Emit per-message trace lines on stderr. Off by default so the stdio JSON-RPC channel stays clean.
VULKRO_OFFLINE=1Inherited from scan: disables every outbound HTTP call.
Every other VULKRO_* varInherited 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

A dedicated extension lives in editor-extensions/vscode. It is a thin client over vulkro lsp: diagnostics in the gutter and Problems pane, hover bubbles, and Suppress / Explain quick-fixes. Build and install from source:

cd editor-extensions/vscode
npm install
npm install -g @vscode/vsce
vsce package # produces vulkro-<version>.vsix
code --install-extension vulkro-*.vsix # or "Install from VSIX" in the UI

Cursor and Windsurf are VS Code forks and install the same .vsix. The extension requires the vulkro binary on PATH (or set vulkro.lsp.path).

JetBrains IDEs (IntelliJ, PyCharm, WebStorm, GoLand, Rider, RubyMine)

A Gradle plugin scaffold in editor-extensions/jetbrains wires vulkro lsp through the platform LSP4IJ client (2023.2+). Build with ./gradlew buildPlugin and install the zip from Settings -> Plugins -> Install Plugin from Disk. On any 2023.2+ IDE you can also register vulkro lsp as a generic LSP server 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

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 fix rewrites as CodeAction edits). 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).