Skip to content

Repository files navigation

trivy-ls

A Language Server Protocol server for Trivy. It scans your workspace and reports misconfigurations, secrets and vulnerabilities as editor diagnostics, in any editor with an LSP client.

Built on go-lsp.

Trivy LS - Neovim

Contents

What it does

Feature Behaviour
Diagnostics Misconfigurations, secrets and vulnerabilities published per file
Hover Full description, resolution and advisory link for findings on the hovered line
Code actions Insert a trivy:ignore comment, add the check to .trivyignore, open the advisory
Commands trivy-ls.scan, trivy-ls.addToIgnoreFile, trivy-ls.openUrl
Progress $/progress notifications while a scan runs
Live config Settings can be changed via workspace/didChangeConfiguration without a restart

Severities map onto LSP as: CRITICAL/HIGH → error, MEDIUM → warning, LOW → information, UNKNOWN → hint.

Installation

trivy-ls shells out to the trivy binary, so install that first:

# see https://trivy.dev/latest/getting-started/installation/
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

Then install the server. VS Code users can skip this — the extension bundles the matching binary, so installing it is all that's needed.

go install github.com/owenrumney/trivy-ls/cmd/trivy-ls@latest

Or download a binary for your platform from the releases page. If you used go install, make sure $(go env GOPATH)/bin is on your PATH. Check it works:

trivy-ls --version

Configuration

All options are optional. Pass them as initializationOptions (preferred) or via workspace/didChangeConfiguration. Both a bare object and one nested under a trivy key are accepted, since clients differ.

Option Type Default Description
trivyPath string trivy Path to the Trivy binary
scanners string[] ["misconfig","secret","vuln"] Passed to --scanners
severities string[] all Passed to --severity, e.g. ["HIGH","CRITICAL"]
ignoreFile string .trivyignore Passed to --ignorefile
configFile string Passed to --config
extraArgs string[] Appended to the Trivy command line verbatim
scanOnSave bool true Rescan the workspace when a file is saved
scanOnOpen bool true Scan the workspace once at startup
fullRange bool false Underline a finding's whole span instead of just its first line

First run may be slow. The vuln scanner downloads Trivy's vulnerability database (~50 MB) on first use. Drop it from scanners if you only care about IaC and secrets — misconfig and secret scanning are entirely offline.

fullRange is off by default because IaC findings routinely cover an entire resource block, and a twenty-line squiggle is harder to read than a single underlined line.

Editor setup

VS Code

Install the Trivy Language Server extension from the Marketplace or Open VSX. It bundles the matching trivy-ls binary for your platform, so Trivy itself is the only other thing you need.

Settings live under the trivy-ls. prefix and mirror the table above, plus trivy-ls.serverPath to point at your own build of the server:

{
  "trivy-ls.scanners": ["misconfig", "secret"],
  "trivy-ls.severities": ["HIGH", "CRITICAL"],
  "trivy-ls.fullRange": false
}

Two commands are contributed: Trivy LS: Scan Workspace and Trivy LS: Restart Language Server.

The extension is deliberately minimal — findings go to the Problems panel and the editor gutter, with hover details and ignore quick fixes. It has no tree view. If you want the tree view, or Aqua Platform integration, use the official Trivy extension instead.

Running both extensions together is supported but redundant: each runs its own scan and both publish diagnostics, so every finding appears twice. This extension namespaces its settings and commands trivy-ls. rather than trivy. precisely so the two can coexist without clashing.

Neovim

Neovim 0.11+, no plugins needed:

vim.lsp.config['trivy_ls'] = {
  cmd = { 'trivy-ls' },
  root_markers = { '.git', '.trivyignore', 'main.tf' },
  filetypes = { 'terraform', 'hcl', 'yaml', 'json', 'dockerfile' },
  init_options = {
    scanners = { 'misconfig', 'secret' },
  },
}

vim.lsp.enable('trivy_ls')

With nvim-lspconfig on older versions:

local configs = require('lspconfig.configs')
local lspconfig = require('lspconfig')

configs.trivy_ls = {
  default_config = {
    cmd = { 'trivy-ls' },
    filetypes = { 'terraform', 'hcl', 'yaml', 'json', 'dockerfile' },
    root_dir = lspconfig.util.root_pattern('.git', '.trivyignore'),
    init_options = { scanners = { 'misconfig', 'secret' } },
  },
}

lspconfig.trivy_ls.setup({})

This runs alongside terraform-ls or yamlls — Neovim merges diagnostics from every attached server.

Helix

In ~/.config/helix/languages.toml:

[language-server.trivy-ls]
command = "trivy-ls"
config = { scanners = ["misconfig", "secret"] }

[[language]]
name = "hcl"
language-servers = ["terraform-ls", "trivy-ls"]

[[language]]
name = "yaml"
language-servers = ["yaml-language-server", "trivy-ls"]

[[language]]
name = "dockerfile"
language-servers = ["docker-langserver", "trivy-ls"]

Helix passes config through as initializationOptions.

Vim / coc.nvim

In :CocConfig:

{
  "languageserver": {
    "trivy": {
      "command": "trivy-ls",
      "filetypes": ["terraform", "hcl", "yaml", "json", "dockerfile"],
      "rootPatterns": [".git", ".trivyignore"],
      "initializationOptions": {
        "scanners": ["misconfig", "secret"]
      }
    }
  }
}

Emacs

With eglot:

(add-to-list 'eglot-server-programs
             '((terraform-mode yaml-mode dockerfile-mode) . ("trivy-ls")))

With lsp-mode, register it as an add-on so it runs alongside your primary server:

(lsp-register-client
 (make-lsp-client
  :new-connection (lsp-stdio-connection '("trivy-ls"))
  :major-modes '(terraform-mode yaml-mode dockerfile-mode)
  :add-on? t
  :server-id 'trivy-ls
  :initialization-options (lambda () '(:scanners ["misconfig" "secret"]))))

Sublime Text

With the LSP package, in LSP.sublime-settings:

{
  "clients": {
    "trivy-ls": {
      "enabled": true,
      "command": ["trivy-ls"],
      "selector": "source.terraform | source.yaml | source.json | source.dockerfile",
      "initializationOptions": {
        "scanners": ["misconfig", "secret"]
      }
    }
  }
}

JetBrains IDEs

IntelliJ IDEA, GoLand and friends have no user-facing way to add an arbitrary LSP server out of the box. Install the LSP4IJ plugin, then add a New Language Server with:

  • Command: trivy-ls
  • File name patterns: *.tf, *.yaml, *.yml, Dockerfile*
  • Configuration → Initialization options: {"scanners": ["misconfig", "secret"]}

Zed

Zed only launches language servers provided by an extension — there's no settings key for an arbitrary binary. Running trivy-ls in Zed means writing a small Zed extension that returns trivy-ls from its language_server_command. There isn't one published yet.

How scanning works

Trivy scans a workspace, not a buffer, and takes seconds rather than milliseconds. That shapes the design:

  • Scans run on a background goroutine. A request arriving mid-scan queues at most one follow-up, so holding down save doesn't stampede the binary.
  • Scans are triggered at startup, on save, on workspace/didChangeConfiguration, and by the trivy-ls.scan command. There is no scan-on-keystroke — Trivy reads from disk, so it would see stale content anyway.
  • After each scan the server clears diagnostics for files that had findings previously but no longer do, so fixed findings disappear.
  • Findings for targets that aren't real files under the workspace root — Trivy's synthetic . target, or downloaded Terraform modules — are dropped, since they can't carry a diagnostic.
  • Checks that assert the absence of something (No HEALTHCHECK defined, Image user should not be 'root') carry no line number from Trivy, so they're reported against line 1 of the file.

Ignoring findings

Two code actions are offered on any finding:

  • Ignore on this line inserts a #trivy:ignore:<ID> comment above the offending line, matching its indentation. Not offered for JSON, which has no comment syntax, or for findings with no line to anchor to.
  • Ignore workspace-wide appends the check ID to .trivyignore (creating it if needed) and rescans.

Development

make build     # build to bin/trivy-ls
make test      # unit tests plus end-to-end tests against a fixture workspace
make lint      # golangci-lint

The VS Code client lives in vscode-trivy-ls/:

make extension                                   # cross-compile and package every platform .vsix
make extension-target VSCE_TARGET=darwin-arm64   # just one platform
make extension-install                           # build for this platform and install into VS Code

Each .vsix is platform-specific and carries a single server binary in bin/, so the extension never downloads anything at runtime.

The end-to-end tests boot a real server over an in-memory pipe and scan internal/handler/testdata/workspace. They skip automatically if trivy isn't installed.

To inspect the LSP traffic while developing, start the server with the debug UI:

trivy-ls --debug-ui localhost:9000

Licence

MIT

About

Language server bringing Trivy misconfiguration, secret and vulnerability findings into any LSP-capable editor as inline diagnostics.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages