← All articles

Findings where you're already looking: SARIF in your editor and the Security tab

July 29, 2026 · Wilson Santos

The distance between a finding and the line that caused it is the single best predictor of whether anyone fixes it.

A finding in a CI log is read once, by the person who broke the build, in a hurry. A finding in a dashboard is read at the weekly triage meeting, by someone who didn't write the code. A finding sitting as a squiggle under the line you're already editing is fixed in the next thirty seconds, because fixing it is easier than looking at it.

None of that requires an extension from us. Draugr writes standard SARIF 2.1.0, and every major editor already reads SARIF. What it does require is caring about a handful of fields that are easy to leave out, and about what the file says when a viewer renders it.

The short version#

draugr scan . -o .draugr-out

Open .draugr-out/results.sarif in VS Code with Microsoft's SARIF Viewer, or in a JetBrains IDE via the Qodana plugin's Open SARIF report. Findings land in the Problems panel and as markers on the offending lines. There is no third step.

Add .draugr-out/ to .gitignore; it's build output.

What had to be right for that to work#

SARIF is a permissive format. You can emit something a validator accepts that is nearly useless in an editor. Four things make the difference.

uriBaseId and originalUriBaseIds. Our locations are repo-relative, and the run declares what they're relative to:

"originalUriBaseIds": {
  "%SRCROOT%": { "description": { "text": "The root of the scanned source tree." } }
}

Without that declaration, a viewer opening app/Dockerfile doesn't know which app/Dockerfile you meant, so it prompts you to locate the file, once, per file. It's the difference between a report that opens and a report you close.

This matters more than it sounds because Draugr scans a clean checkout of the committed revision, in a temp directory, not your working tree. The absolute paths in the scanners' raw output point at /tmp/draugr-repo-XXXX/..., which exists on nobody's machine including yours five minutes later. Rewriting those to repo-relative paths with a declared base is what makes the findings portable at all.

helpUri on every rule we can. A rule id is a name, not an explanation. DS-0002 tells you nothing; one click to Aqua's page for DS-0002 tells you it wants a USER statement in your Dockerfile and why. Nearly every rule in a demo scan carries one. Where the scanner doesn't publish a link we synthesize the obvious one, NVD or GitHub Advisories for CVE-/GHSA- ids, the SPDX page for a license.

Rule descriptions and remediation prose, relayed intact. Each scanner's own shortDescription, fullDescription and help text travel into our SARIF unchanged. That's the detail pane in the viewer.

Messages that read as sentences. Some scanners write their finding message as a field dump rather than prose, Trivy's opens Artifact: app/Dockerfile, with what's wrong five lines down:

Artifact: app/Dockerfile
Type: dockerfile
Vulnerability DS-0002
Severity: HIGH
Message: Specify at least 1 USER command in Dockerfile with non-root user as argument

Most viewers show the first line in the list, so fourteen Kubernetes misconfigurations in one manifest would read as fourteen consecutive rows saying Artifact: deploy/pod.yaml, sorted by the filename the panel already shows in its own column. Draugr detects that shape when it decodes a scanner's output and promotes the rule's summary instead, so the row reads Image user should not be 'root'. The scanner's full detail is untouched and still sits on the rule, which is what the viewer shows beside a selected finding.

Because it happens at the point a tool's output becomes Draugr's model rather than at the point something is printed, the editor, the PR annotations and an MCP client all get the same readable text.

Findings also keep their Draugr-computed priority (P1–P4) and numeric severity in the property bag, so a viewer that sorts by severity sorts sensibly.

The flag not to use here#

The previous post here argued that agents should read --format sarif --compact, which is 3.8× smaller than the alternative. It gets there by dropping shortDescription, fullDescription, help and name from the rules.

Those are precisely the four fields a viewer displays beside a finding.

So: compact for machines, full for humans, one flag, and it's the right split. The two consumers want different things. An agent wants rule ids it can look up on demand; a person wants the paragraph right there, without leaving the editor. A single format serving both would be too big for one and too thin for the other, which is why this is a flag rather than a compromise.

One thing worth knowing#

Line numbers are relative to the committed revision. Repository scans run against a clean checkout, not your working tree, deliberately, because scanning your dirty tree would hand you findings that don't reproduce in CI, and a finding you can't reproduce is worse than no finding. The consequence is that if you've added twenty lines above a marker since your last commit, it sits twenty lines off. Commit, or re-scan, and it's exact.

Three places, same findings#

The editor is one of three. Which one you want depends on when you want to be interrupted:

WhereWhat you getWhat it needs
Your editorInline squiggles, Problems list, click-to-lineresults.sarif + any SARIF viewer
A pull requestAnnotations on the diff, GitHub Security tabThe github publisher or the Action
Your terminalpath:line you can click, linked rule idsNothing, it's the default

The pull-request row has a qualifier worth stating, because it decides whether anyone reads it: the annotations are the findings that pull request introduced, not every finding in the repository. A reviewer shown three hundred alerts they did not cause cannot pick out the four they did, and a review surface that costs more to read than it returns stops being read. The Action diffs the branch against its base and uploads that; the complete scan is still what your default branch publishes.

That last one is worth a mention because it's free and people miss it. The Location column in the console's ranked findings table prints path:line, which VS Code's integrated terminal, JetBrains' terminal and most modern emulators turn into a link that opens the file at that line. The rule id in the same row is a hyperlink to its documentation. Terminals without hyperlink support just show plain text, so nothing breaks.

Run draugr from the repository root for the paths to resolve.

The general point#

Every one of these is standard-format plumbing. We didn't build an editor integration; we made sure the file we already write is good enough that the integrations other people built work properly. That's a much cheaper way to be in three places at once, and it means an editor we've never heard of works too. results.sarif is plain JSON with no Draugr-specific extensions, so a Neovim quickfix converter is a short script over runs[].results[].locations[].physicalLocation.

The full setup, including JetBrains and the .gitignore you'll want, is in See findings in your editor, but the short version is one command and an extension you may already have:

draugr scan . -o .draugr-out

The findings were always there. The only question was whether you'd be looking at them in time for it to be cheap to care.