Quickstart
This guide takes you from zero to a security verdict, then shows how discovery can write the descriptor for you. If you haven't installed Draugr yet, start with install.
Contents: Fastest path · Describe your app · Scan · Focus: what to fix first · Discovery · Run it in CI · Troubleshooting
0. Fastest path (zero-config)#
No descriptor needed. Point Draugr at a repository:
draugr scan . # scans the current repo with sca, secrets, sast, iac
That's the whole path to a verdict. Nothing to write first. When you want to pick controls, add container images or endpoints, or classify components for prioritization, scaffold a descriptor:
draugr init # writes a stack-detected draugr.saga.yaml to customize
The rest of this guide covers that descriptor-driven flow.
1. Describe your app#
Create draugr.saga.yaml. The Saga is the one artifact that maps your software to the
controls that must pass. A minimal, runnable example:
project: my-app
release:
version: "1.0"
config:
controllers:
images:
enabled: true
components:
- name: web
images:
- image: alpine:3.19
A control only runs when it is enabled (globally under config.controllers, or on a
component). See write your first Saga for a gentle walkthrough, or the
Saga schema for every field.
Tip. Turn on editor support first. A Saga written with schema-backed completion is quicker and harder to get wrong: your editor offers the valid control names,
exposureandcriticalityvalues, and flags typos immediately. Most editors need no setup.*.saga.yamlis registered with SchemaStore, anddraugr initwrites a$schemaline for the ones that aren't covered; see editor support for VS Code, JetBrains and Neovim.
2. Scan#
draugr scan draugr.saga.yaml
Draugr plans the work (controllers × components), runs the scanners concurrently, merges and deduplicates results as SARIF, judges them against a policy, and prints a human console summary by default (verdict, priority/severity counts, and the top findings to fix first):
Draugr · PASS (my-app 1.0)
Controls:
images pass no findings
No findings. ✓
For a machine-readable report use --format json (or write artifacts with -o out/):
{
"release": { "name": "my-app", "version": "1.0" },
"verdict": "pass",
"controls": [
{ "name": "images", "verdict": "pass", "highest": "none",
"threshold": "error", "errors": 0, "warnings": 0, "notes": 0, "total": 0 }
],
"stats": { "jobs": 1, "concurrency": 8, "scans": 1, "cacheHits": 0, "deduped": 0 }
}
The verdict and counts depend on what the scanners find, a real image like alpine:3.19 will
typically report several vulnerabilities, so you'll see fail unless you use a minimal image or
raise --fail-on. The process exits non-zero when the verdict is fail, so it gates a pipeline
directly.
Useful flags:
draugr scan draugr.saga.yaml -o out/ # write out/report.json + out/results.sarif
draugr scan draugr.saga.yaml --fail-on medium # stricter gate (default: high)
draugr scan draugr.saga.yaml --cache-dir .draugr/cache # skip re-scanning unchanged targets
draugr scan draugr.saga.yaml --min-priority P2 # list only the findings worth acting on now
draugr scan draugr.saga.yaml --fail-on-priority P1 # also fail the gate on any P1 finding
See the CLI reference for every flag.
Focus: what to fix first#
Classify your components. The fastest way to set up prioritization is the guided wizard. It asks
a few questions per component and writes exposure and criticality back into your Saga (comments
and formatting preserved):
draugr classify
Component: web
Exposure, who can reach it?
1) public anyone on the internet can reach it, no sign-in
2) authenticated on the internet, but behind a login
3) internal only from inside your own network or VPN
4) restricted inside your network and locked down further, an allowlist, a private link, its own segment
Choose [1-4]: 1
Criticality, what happens if it fails or is breached?
1) critical an outage or data loss for the business
2) important degraded service, but no outage
3) supporting limited impact, easily worked around
Choose [1-3]: 1
→ web: exposure=public, criticality=critical
(Prefer to hand-edit? The fields are in the Saga schema. And
draugr survey on a k8s namespace already proposes exposure for you.)
Once components declare exposure and criticality, Draugr ranks every finding into a priority
band, combining the finding's severity with how exposed and how business-critical its component is.
The report always includes a priorities count (P1–P4); --min-priority adds a ranked findings
list of just those at or above the band, so you can act on the short list instead of the whole wall:
{
"priorities": { "p1": 2, "p2": 5, "p3": 3, "p4": 0 },
"findings": [
{ "priority": "P1", "level": "error", "score": 9.1, "control": "sca",
"ruleId": "CVE-2025-0001", "message": "…", "location": "go.mod" }
]
}
P1 = act now · P2 = this cycle · P3 = backlog · P4 = track. A component left unclassified is treated as high-risk so nothing slips.
Gate on priority. --fail-on-priority P1 fails the build when any finding reaches that band,
component-aware gating without a per-component config, since priority already folds in exposure and
criticality. It composes with the level gate (--fail-on): the run fails if either trips. Each
control also reports its highestPriority as evidence. See
prioritization for how the bands are computed.
3. Let discovery write the descriptor#
Instead of hand-writing components, point a surveyor at your environment:
# Repositories in a GitHub org (GITHUB_TOKEN env var, or a token in scope config)
GITHUB_TOKEN=*** draugr survey github repos --org my-org -o draugr.saga.yaml
# Unique container images running in a Kubernetes namespace (uses your kubeconfig)
draugr survey k8s images --namespace prod -o draugr.saga.yaml
A survey adds to an existing Saga rather than overwriting it. The descriptor holds decisions a
survey cannot rediscover. Pass --replace to start again. See the surveyors
reference for what each one discovers.
4. Run it in CI#
scan's exit code is the gate, so Draugr runs anywhere that can run a binary. Each of the three
big platforms has a first-party integration that does the install, the mode selection and the
reporting for you:
| Platform | Add | Findings land in |
|---|---|---|
| GitHub Actions | uses: draugr-dev/draugr@v0 | code scanning (Security tab), a sticky PR comment |
| GitLab CI | include: remote: …/gitlab-ci/draugr.yml | the merge request's Reports tab, and on Ultimate the Vulnerability Report, Dependency List and License Compliance |
| Azure Pipelines | - template: azure-pipelines/draugr.yml@draugr | the Tests tab, a sticky PR comment |
All three do the same two things: scan the branch and gate it, and on a pull or merge request report only what the change introduced rather than the backlog it inherited.
- GitHub Action guide · code scanning
- GitLab guide, one include and one masked variable
- Azure Pipelines guide
Anywhere else, Jenkins, a laptop, a cron job, install the binary and run draugr scan; the exit
code is the whole contract.
5. See the findings in your editor#
CI tells you at the end; your editor tells you while you're writing. draugr scan -o out writes
out/results.sarif, which VS Code and JetBrains read as inline diagnostics, squiggles on the
offending lines, and click-to-line from a Problems list, with no Draugr-specific extension. See see
findings in your editor.
Troubleshooting#
- Not sure what's installed?. Run
draugr doctor draugr.saga.yamlfor a preflight: it validates the descriptor and lists every scanner the Saga needs as found / missing / version, with an install hint for each. Use it as a CI gate:draugr doctor saga.yaml && draugr scan saga.yaml. - Sure it ran, but did it look at everything?. The same preflight answers that. Doctor lists
any surface the descriptor declares that no enabled control examines, so a component with images
and the
imagescontrol switched off is reported before the scan passes over it rather than after. Add--fail-on-uncoveredto make that a failure when the descriptor is meant to be complete. - No findings / control didn't run. Ensure the control is
enabledand the component has the relevant resources (e.g.imagesfor the images control). executable file not found, the scanner for a control isn't onPATH; rundraugr doctorto see exactly which tool is missing and how to install it.- Descriptor errors. Run
draugr validate draugr.saga.yamlto check the Saga against the schema without running any scanners (good in a pre-commit hook or CI lint step). - Verbose output. Add
--log-level debug(optionally--log-format text).