SAST · SCA · secrets · IaC · containers, from one file, in your own CI

Know which findings to fix first.
Across the scanners you already run.

One file runs the ones your app needs, and then Draugr ranks everything they find against what the code cannot say: who can reach this service, what the business loses when it stops, and whether anybody is exploiting it today.

Open source · Runs in GitHub Actions, GitLab CI and Azure Pipelines

How that's possible, you tell it what the scanner can't know

# draugr.saga.yaml
project: payments-api
release:
  version: "1.4.0"
components:
  - name: api
    exposure: public       # strangers reach it
    criticality: critical  # the business stops without it
    repositories:
      - url: https://github.com/acme/api.git

The same CVE is P1 on a public payments API and P3 on an internal batch job. draugr init writes the first draft.

Two more signals, before anything is ranked

↑ KEV · EPSS

On CISA's known-exploited list makes a finding critical; a high EPSS probability, its estimate of exploitation within 30 days, raises it one band.

↓ Reachability

On Go projects, govulncheck proves your code never calls the vulnerable function and the finding drops one band. It is never deleted.

That is the noise gone, and not by a filter that hides things but by an order.

Why priority ≠ severity →EPSS & KEV →Reachability →

Try it

curl -fsSL https://draugr.dev/install.sh | sh
draugr scan .
⋯
gitleaks, semgrep and trivy are not installed, so iac, sast, sca and secrets would not run.
Install them now with draugr tools install gitleaks semgrep trivy? [Y/n/never]

Run draugr scan . inside a git repository and findings come out most urgent first. On a terminal it asks before installing a scanner it is missing; answer Y and the scan runs.

Until draugr.saga.yaml says who can reach each component and what stops without it, the order is severity alone. draugr init writes the file, draugr classify records those two answers in it, and CI scans it once it is committed.

Commit the draugr.saga.yaml that draugr init writes, and a pull or merge request fails on a P1 finding it introduces.

GitHub Actions

steps for a pull_request job with contents: read and pull-requests: writeGuide →
- uses: actions/checkout@v4
  with: { fetch-depth: 0 }
- uses: draugr-dev/draugr@v0
  with: { saga: draugr.saga.yaml, tools: true, fail-on-new: P1 }

GitLab CI

in .gitlab-ci.ymlGuide →
include:
  - remote: 'https://raw.githubusercontent.com/draugr-dev/draugr/v0.166.3/gitlab-ci/draugr.yml'
stages: [test]

Azure Pipelines

in azure-pipelines.yml, after copying the template to .azure/draugr.ymlGuide →
# on Azure Repos, pull requests also need a build validation policy
steps:
  - template: .azure/draugr.yml
    parameters: { saga: draugr.saga.yaml }

Your assistant calls Draugr through MCP, so the draugr binary must be on your PATH.

Claude Code

Guide →
/plugin marketplace add draugr-dev/draugr
/plugin install draugr@draugr

Other MCP clients

in the MCP Registry as dev.draugr/draugr
{
  "mcpServers": {
    "draugr": { "command": "draugr", "args": ["mcp"] }
  }
}

What it checks, and what does the checking

Code, infrastructure code, dependencies, secrets and licenses; the images, clusters and cloud accounts it ships to; certificates, signatures and the running app, each checked by a scanner you already trust.

Every check, and what runs it →Already pay for something better? It goes in the same slot →SBOM and VEX, for the auditor →Findings where your team already looks →

And what comes out is a list of things to do

$ draugr scan . --view actions

WHAT TO DO  50 actions clear 1154 findings

Bump a dependency  12 actions clear 559 findings
  P1  Upgrade jquery 1.8.3 → 1.12.2
      component storefront · control sca · 4 findings · web/package-lock.json:12 · NSWG-ECO-329 +3
  ⋯

Change the code  14 actions clear 23 findings
  P1  Fix “Found user-controlled data used in a system call”
      Semgrep: dangerous-system-call · Bandit: B605
      component api · control sast · 2 findings · app/app.py:16
  ⋯

Change the infrastructure  21 actions clear 40 findings
  P1  Fix KSV-0118 “Default security context configured”
      control iac · 2 findings · deploy/pod.yaml:6 · KSV-0118 +1
  ⋯

A row is an action, not a finding. Six vulnerabilities in one library are one upgrade, ranked by the worst thing it clears rather than by how many. Each kind of work has its own list, ordered by values the report records, so every position can be checked.

Two things that decide whether a team keeps it on

draugr diff

Gate a pull request on what it introduced, not the backlog it inherited. The only version of this a team can switch on with the backlog still there.

Wire it into CI →

config.exclude

One way to excuse a finding, in the file that already describes your scope. It carries a reason and the date it lapses, and its git history records who accepted it.

Every field it takes →

Those two meet: when an exclusion reaches its expiry the finding comes back on the next pull request, with nobody keeping a calendar.

Gating on what a change introduced →What a defensible exception carries →Every flag on the diff →

Where you already are

The answer, in your editor and your assistant.

In your editor

Underlines on the lines that caused them, in VS Code and JetBrains. Nothing to install.

See findings in your editor →

In your coding assistant

draugr mcp gives it your committed descriptor, so it answers with the same verdict as CI instead of a scope it chose for itself.

In the MCP Registry as dev.draugr/draugr, and as a Claude Code plugin.

Use Draugr from an AI assistant →

Make the fast path the secure path.

Open source, released, and it runs where you run it. Draugr Server keeps the history across an organization and is in early access.