← All articles

Your coding assistant is already answering security questions

July 26, 2026 · Wilson Santos · updated August 8, 2026

Open a repository in an AI coding assistant and ask it to check the code for security problems.

You will get an answer. It'll read some source, probably notice the obvious things, maybe run a scanner if one happens to be on your PATH, and give you a considered paragraph. It'll sound right. The uncomfortable question is what it was looking at when it decided.

What it looks at#

We measured this on draugr-demo, a small Flask app built to have problems, made of 35 lines of Python, a requirements.txt, a Dockerfile, one Kubernetes manifest.

The unaided path, meaning the four scanners an assistant would plausibly reach for, run over the source tree, produced 56 findings across roughly 197 KB of output in four different schemas. Not a bad haul, and The three command-injection paths in app.py are all in there.

Draugr, scanning the same repository through its committed descriptor, found 439.

The extra 383 aren't noise we invented but sit in python:3.8-slim, the base image the app ships, ten critical and sixty-eight high. And here is the number that matters:

78 of the 99 highest-priority findings were in that image. The source-tree scan never looked at it.

Not because the assistant is bad at its job. Because nothing in the source tree said the app ships a container image. The Saga says so, in one line:

components:
  - name: api
    exposure: public
    images:
      - image: python:3.8-slim

"It would have found the Dockerfile"#

Maybe. A thorough assistant could read FROM python:3.8-slim and think to scan the base image. Ours didn't, and we didn't rig that, but a better prompt or a more careful model might.

So the claim isn't that it's impossible, but something narrower and harder to argue with:

It's left to chance. Whether the image gets scanned depends on the model, the prompt, and the day. The descriptor doesn't depend on any of them.

It doesn't generalize. A Dockerfile is one place images come from. Plenty of components ship images built by CI, tagged elsewhere, or pulled from a registry with nothing in the repo naming them. And images are the easy case. The endpoints to probe, the infrastructure to check, whether this component is internet-facing at all. None of that is inferable from source, and all of it is one line in a descriptor.

That's the whole argument, and it isn't about model quality:

Scope is a fact about your organization, not about your code.

It tells you what it checked#

A scan comes back with the controls that ran, and with any surface your descriptor declares that none of them looked at:

{
  "verdict": "fail",
  "controls": ["sast", "sca", "secrets"],
  "uncovered": ["api declares images, and images is not enabled"]
}

Coverage is something you read, not something you infer from an absence of findings. That is the difference worth paying for, and it is sharper than "a scanner finds more things".

Consider what a confident answer costs when nothing states its scope. An assistant that greps for credentials across *.py, finds none, and writes "no hardcoded secrets" has told you a true thing about Python files and filed it as a fact about the repository. The grep was right. The sentence was not. Nothing in the output marks the difference, and the sentence is the part you act on.

A declared scope cannot make that trade. The controls are chosen before the run and reported after it, so "we did not check that" and "we checked that and it was clean" never arrive looking the same. Your assistant gets the same distinction, which is why it keeps going instead of stopping at a verdict.

The other half, with nothing to rank by#

Even where the unaided scan found the same finding, it couldn't tell you what to do about it.

Severity ranks a vulnerability in the abstract. Priority needs to know how reachable the thing is and how much you'd care if it broke, which is a different question, and one no scanner can compute, because the answer isn't in the code. exposure: public on that component is why its findings sort to the top.

So the assistant hands you a list. Draugr hands you an order.

Two answers, one of which is wrong#

The deeper problem is that you now have two sources of truth. The assistant told you one thing in your editor; CI will tell you another on push. They used different scopes, different scanners and no shared notion of priority, so of course they disagree, and the one that blocks your merge is the one you didn't see.

That is the case for wiring them together, and it is not that AI is exciting but that one descriptor gives one answer.

Point it at Draugr#

claude mcp add draugr -- draugr mcp

Draugr speaks the Model Context Protocol, so your assistant can ask it directly:

ToolWhat it answers
list_controlsWhich controls exist, what each checks, which scanner backs it, and the options each scanner takes
get_saga_schemaThe descriptor schema your build enforces
validate_sagaWhether a descriptor is valid, and why not
check_toolsWhich scanners are installed, and the command to fix what isn't
summarize_reportAn existing scan, ranked by priority, each finding carrying its remediation and the version that fixes it
fix_listThe same scan as things to do, one row per fix, saying how many findings each clears and which release clears them
explain_ruleWhat a check means and how to fix it, out of the report already on disk
diff_reportsWhat a change introduced and what it resolved, and whether a pull-request gate would fail
list_surveyors / surveyWhat a live cluster or organization is made of, as a descriptor to review
scanA fresh scan and verdict, the controls it covered, and where your publishers filed it, only if you allow it

Every *.saga.yaml nearby is also exposed as a resource, so the assistant reads your committed scope without being told where it is.

fix_list is the one that changes what an assistant can say. A ranked list of findings is still a list of things that are wrong; one change usually clears many of them, and an image somebody else publishes is one action however many packages are wrong inside it. Asking for the actions rather than the findings is the difference between here are forty problems and here are five things to do, the first clears thirty-nine of them, and here is the release to move to. It is the same grouping the terminal prints, so the two cannot describe one report differently.

explain_rule closes the gap that ranking leaves behind. A rule id and a truncated line is enough to order a finding and not enough to decide anything, and the remediation the scanner published is already in the report, so an assistant should read it rather than fetch whatever a search engine offers for the identifier. For a benchmark check, what it would otherwise find is a registration form in front of a PDF.

check_tools is the one that closes a loop. A control whose scanner is missing can't run, and Draugr reports that as a failure rather than a pass, so an assistant that hits it can say what's wrong and hand you draugr tools install trivy. It won't run that itself, because installing binaries is a write to your machine, and your client already has a permission model for commands that's stronger than anything this server could offer.

get_saga_schema is the one that surprised us. The schema comes from the binary you have installed, not from the web, and it rejects unknown keys, so an invented field name fails loudly instead of being silently ignored. Paired with validate_saga taking inline content, an assistant can check a descriptor before writing it to disk.

Scanning is off by default#

A scan clones repositories, executes external scanners and reaches the network. An assistant setting that off because it was curious is a bad surprise, so the scan tool isn't registered unless you ask:

draugr mcp --scan=ask      # you approve each call
draugr mcp --scan=always   # no prompt, sandboxes and CI

--scan=ask is the one to want, giving permission for the scan in front of you rather than for every scan this session, and the prompt describes that scan. It names the controls, how many components they cover, any scanner that does more than read, whether anything sends traffic to a host you declared, and where the results will be filed. Approving five read-only controls over a checkout and approving a probe of a production endpoint are different decisions, and you are told which one you are making.

It needs a client that implements MCP elicitation, and if yours can't prompt, the scan is refused with a message saying so rather than quietly running anyway.

Everything else is read-only and free to call. Leaving scanning off is a fine default, since summarize_report answers what should I fix first? from a scan your pipeline already ran, at no cost at all.


Your assistant is going to answer this question either way. The only choice is whether it answers from something your team wrote down, or something it made up on the spot.

Set it up →