rsigma-lsp is a Language Server Protocol (LSP) server that brings real-time Sigma rule development support to any editor — VSCode, Neovim, Helix, Zed, Emacs, and more. Built on the same parser, linter, and compiler as the CLI.
This binary is part of rsigma.
cargo install --path crates/rsigma-lsp| Capability | Details |
|---|---|
| Text document sync | Full (entire document on every change) |
| Diagnostics | Lint (65 rules), parse errors, compile errors |
| Code actions | Quick-fix actions for auto-fixable lint warnings |
| Completions | Context-aware; trigger characters: |, :, , \n |
| Hover | Field modifiers, MITRE ATT&CK tactics and techniques |
| Document symbols | Hierarchical outline of rule structure |
Diagnostics run through three layers, each adding errors from a different stage:
Runs all 65 lint rules from rsigma-parser (Sigma spec v2.1.0). Loads .rsigma-lint.yml config from ancestor directories and respects inline # rsigma-disable comments.
| Lint severity | LSP severity |
|---|---|
| Error | DiagnosticSeverity::ERROR |
| Warning | DiagnosticSeverity::WARNING |
| Info | DiagnosticSeverity::INFORMATION |
| Hint | DiagnosticSeverity::HINT |
Diagnostic code: the lint rule name (e.g. missing_title, invalid_level).
YAML and condition expression parse failures from rsigma-parser.
| Severity | ERROR |
| Code | parse_error |
| Range | Extracted from "at line X column Y" in the error message, or falls back to the first content line |
Per-rule compilation errors from rsigma-eval (unknown selections, invalid modifier combos).
| Severity | ERROR |
| Code | compile_error |
| Range | For "unknown detection identifier: X", highlights X in the condition: line; otherwise falls back to /detection/condition |
All diagnostics have source: "rsigma".
| Event | Behavior |
|---|---|
| Document open | Diagnostics run immediately |
| Document change | Diagnostics debounced at 150 ms |
| Document save | Diagnostics run immediately |
| Document close | Pending diagnostics aborted; existing diagnostics cleared |
Quick-fix code actions for lint warnings with auto-fix suggestions. When the cursor is on a diagnostic that has a safe fix, the editor offers a one-click action to apply it.
| Fixable rule | Action |
|---|---|
non_lowercase_key |
Rename key to lowercase |
logsource_value_not_lowercase |
Lowercase the value |
invalid_status |
Replace with closest valid status |
invalid_level |
Replace with closest valid level |
unknown_key |
Rename to closest known key (typo correction) |
duplicate_tags |
Remove duplicate tag entry |
duplicate_references |
Remove duplicate reference entry |
duplicate_fields |
Remove duplicate field entry |
all_with_re |
Remove redundant |all modifier |
single_value_all_modifier |
Remove redundant |all modifier |
wildcard_only_value |
Replace with |exists: true |
filter_has_level / filter_has_status |
Remove the field |
All code actions are marked as quickfix with isPreferred: true. Only Safe disposition fixes are offered.
Context-aware completions triggered by |, :, (space), and newline. Contexts are evaluated in priority order:
Trigger: | character in the current line.
Offers all 25 field modifiers matching the typed prefix, with descriptions. Completion kind: ENUM_MEMBER.
Trigger: Inside the tags section, on a line starting with - .
Offers all 14 MITRE ATT&CK tactic tags (attack.initial_access, attack.execution, ...) plus extra tags:
cve.detection.dfirdetection.emerging_threatsdetection.threat_huntingtlp.white,tlp.green,tlp.amber,tlp.red
Completion kind: VALUE.
Trigger: Line starts with condition: or cursor is within the condition section.
Offers:
- Selection names extracted from the current rule's
detectionsection (kind:VARIABLE) - Condition keywords:
and,or,not,1 of,all of,1 of them,all of them(kind:KEYWORD)
Trigger: After : for known keys.
| Key | Offered values |
|---|---|
status |
stable, test, experimental, deprecated, unsupported |
level |
informational, low, medium, high, critical |
product |
windows, linux, macos, aws, azure, gcp, m365, okta, github |
category |
27 values (process_creation, file_event, network_connection, image_load, registry_event, dns_query, file_access, file_change, file_delete, file_rename, driver_load, pipe_created, process_access, process_tampering, process_termination, ps_classic_start, ps_module, ps_script, registry_add, registry_delete, registry_set, sysmon_error, sysmon_status, wmi_event, clipboard_capture, create_remote_thread, create_stream_hash) |
service |
20 values (sysmon, security, system, powershell, powershell-classic, taskscheduler, wmi, application, dns-server, driver-framework, firewall-as, ldap_debug, msexchange-management, ntlm, openssh, printservice-admin, printservice-operational, smbclient-security, terminalservices-localsessionmanager, windefend) |
Completion kind: ENUM_MEMBER.
Trigger: Indent level 0, no : on the line.
Offers all 17 top-level keys with snippet placeholders: title, id, related, status, description, references, author, date, modified, tags, logsource, detection, falsepositives, level, fields, correlation, filter.
Completion kind: PROPERTY. Insert format: SNIPPET (with $0, $1, etc.).
Trigger: Inside logsource section, indent > 0, no :.
Offers: category, product, service, definition.
Trigger: Inside detection section, indent > 0, no :.
Offers: selection, filter, condition.
Hover provides documentation at the cursor position. The server checks (in priority order):
Pattern: attack.t* (e.g. attack.t1059, attack.t1059.001).
Shows technique ID as a heading with a link to https://attack.mitre.org/techniques/{id}.
Any of the 14 tactic names (e.g. attack.execution, attack.persistence).
Shows the tactic description.
Any of the 25 modifier names (e.g. contains, base64, windash).
Shows the modifier name and description in Markdown.
Provides a hierarchical outline of the rule structure:
| Symbol | Kind | Condition |
|---|---|---|
title |
STRING |
When present |
logsource |
NAMESPACE |
When present |
correlation |
NAMESPACE |
When present |
detection |
NAMESPACE |
When present; includes child symbols |
Child keys within detection at the first indented level:
| Key | Kind |
|---|---|
condition |
BOOLEAN |
| Other keys (selections) | FIELD |
Each child has a range (full section) and selection_range (key line only).
| Event | Behavior |
|---|---|
textDocument/didOpen |
If .yml/.yaml: run diagnostics immediately, store document |
textDocument/didChange |
Full sync (entire document replaced). If Sigma file: schedule debounced diagnostics (150 ms) |
textDocument/didSave |
If Sigma file: run diagnostics immediately (no debounce) |
textDocument/didClose |
Abort pending diagnostics, remove document, publish empty diagnostics to clear |
Only .yml and .yaml files trigger diagnostics. Non-Sigma files are stored for document state but produce no diagnostics.
All handler functions (completion, hover, document_symbol, diagnostics) are wrapped in catch_unwind. On panic:
- An error is logged.
- Completions return empty; hover returns
None; symbols return empty; diagnostics return empty. - The server continues operating.
If a document URI is not found in the store, handlers return None or empty results.
vim.api.nvim_create_autocmd('FileType', {
pattern = 'yaml',
callback = function()
vim.lsp.start({
name = 'rsigma-lsp',
cmd = { 'rsigma-lsp' },
})
end,
})A thin extension wrapper is provided in editors/vscode/. To use it:
cd editors/vscode
npm install
npm run package # builds with esbuild + creates .vsix
code --install-extension rsigma-0.1.0.vsix # VSCode
cursor --install-extension rsigma-0.1.0.vsix # CursorThe extension launches rsigma-lsp from your $PATH by default. Override via the rsigma.serverPath setting.
[language-server.rsigma-lsp]
command = "rsigma-lsp"
[[language]]
name = "yaml"
language-servers = ["yaml-language-server", "rsigma-lsp"]Set the RUST_LOG environment variable to enable logging:
RUST_LOG=rsigma_lsp=debug rsigma-lspLog messages include initialization, diagnostics errors, and handler panics.
MIT License.