← All articles

Describe your app, not your scanners: one config for Trivy, Semgrep and Gitleaks

July 10, 2026 · Wilson Santos · updated July 26, 2026

Ask ten teams how they run application security in CI and you'll get ten pipelines, each a hand-wired pile of scanner invocations: a SAST step here, a container scan there, a dependency check bolted on, some header probe someone copied from a gist two years ago. Every service reinvents it. Nobody remembers why the flags are set the way they are. And the moment you have more than a handful of services, it stops scaling.

The root problem is that we describe the tools instead of the software.

The tool-centric trap#

When your pipeline is a list of scanner commands, three things go wrong as you grow:

  1. Every repo drifts. Each team tweaks the invocation, so no two services are assessed the same way. "Are we even scanning that service's images?" becomes an unanswerable question.
  2. The results don't come together. Each tool emits its own format and its own severity scale. You end up with five dashboards and no single answer to "can this release ship?"
  3. It's expensive. You re-scan unchanged artifacts on every run because the pipeline has no idea what changed.

You can paper over any one of these. You can't paper over all three at once, not across dozens of services.

Flip it: describe the app#

Developers already know their software. They know where the code lives, what images it builds, what endpoints it exposes, what infrastructure it runs on. That knowledge is stable and small. So capture that, once, declaratively:

project: my-app
release:
  version: "1.0"
components:
  - name: backend
    repositories:
      - url: https://github.com/acme/backend.git
    images:
      - image: registry.example.com/acme/backend:1.0
    hosts:
      - url: https://api.acme.com

From a description like this, the right controls follow automatically: images get an image scan, repositories get SAST and dependency analysis, endpoints get header and TLS checks. The developer never has to know which scanner runs, or how to configure it. They describe their app; the system figures out the rest.

What you declare decides what runs Four rows. Repositories map to the sca, secrets, sast and iac controls, run by Trivy, Gitleaks and Semgrep. Images map to the images control, run by Trivy. Hosts map to headers and tls, which are native and need no tool installed. Infrastructure maps to the infrastructure control, native or kube-bench. You describe Draugr infers the controls and runs what they need repositories sca · secrets · sast · iac Trivy · Gitleaks · Semgrep images images Trivy hosts headers · tls native, no tool to install infrastructure infrastructure native · kube-bench
You write the left column. The other two follow from it.

Why this fixes the three failures#

  • No drift. One descriptor format means every service is assessed consistently. New service? Describe it and you're done. Onboarding goes from days to minutes.
  • Results converge. Normalize every scanner's output to a single interchange format, SARIF is the obvious choice, and you get one deduplicated view and one pass/fail verdict, not five dashboards. As a bonus, SARIF flows straight into GitHub, GitLab, and Azure DevOps security tabs for free.
  • It gets cheap. Because the descriptor names concrete artifacts (an image digest, a commit), you can hash the inputs and skip anything that hasn't changed. Unchanged component, no re-scan. At a hundred services that's the difference between a security gate people tolerate and one they route around.

It answers questions you didn't write it for#

A descriptor written to drive scanners turns out to be useful to anything that needs to know what your software is, and the clearest example is one nobody was planning for when this model was designed.

Coding assistants are asked to check code for security problems constantly. Without something written down, an assistant answers from a scope it picked itself: whatever it can infer from the source tree in front of it. On our demo app that missed 78 of the 99 things you'd fix first, all of them in a container image the repository never mentions, because nothing in a source tree says which image the app ships.

Scope is a fact about your organization, not about your code. Once it's written down, an assistant reads your attack surface instead of guessing at it.

The quiet win: security stops being a bottleneck#

The tool-centric model makes security a specialist chore that slows delivery. The descriptor model makes it a property of the software that travels with it. Developers self-serve, the platform team sets sane defaults once, and the security team reviews evidence instead of babysitting pipelines. Everybody moves faster, which, counter to the usual framing, is exactly when security gets better, because the fast path is finally the secure path.

That inversion, describe your app, not your scanners, is the whole idea behind Draugr.