VulnDep AutoFixer
VulnDep AutoFixer is a GitLab Duo Agent that autonomously remediates dependency vulnerabilities in a live codebase. Trigger: @vulndep-autofixer fix vulns Actions taken: reads source files → patches code → commits changes → runs tests → opens merge request → syncs security dashboard. This is not a chatbot. It writes code, commits it, and ships a merge request.
What Inspired Us
Every developer on a team has lived through the same frustrating cycle: a security scanner flags a vulnerable dependency, you bump the version, and suddenly half your codebase breaks. You spend the next two hours reading migration guides, patching API changes, re-running tests, and writing a MR description that nobody reads carefully enough. Then it happens again next week with a different package.
We kept asking the same question — why is a human doing all of this?
The detection is automated. The fix is usually mechanical. The code changes follow patterns. The tests already exist. The MR template is always the same. Every single part of this workflow is something an AI agent should be able to handle — yet every existing tool stops at "here's a PR that bumps the version number" and leaves the hard parts to you.
That gap is what VulnDep AutoFixer was built to close.
What We Built
VulnDep AutoFixer is a GitLab Duo Agent that handles the entire dependency vulnerability remediation workflow autonomously:
- Scans your dependencies for known CVEs using GitLab's native security tooling
- Assesses risk before touching anything — scoring exploitability, breaking change likelihood, and codebase impact
- Updates vulnerable packages to safe versions
- Reads your actual source files and patches any breaking API changes the version bump introduces
- Enforces your team's coding standards on every edit it makes
- Runs your test suite and validates the result
- Creates GitLab Issues for anything it cannot fix automatically — nothing gets silently skipped
- Ships a fully documented merge request with a before/after vulnerability table, files patched, linter status, and test results
- Syncs the GitLab Security Dashboard so your security posture stays accurate
The key architectural decision was building it as a three-agent orchestration system — a Scanner Agent, a Fixer Agent, and a Validator Agent — where each agent communicates with the next via a structured JSON handoff document rather than free-text context. This eliminates the ambiguity that causes single-agent pipelines to hallucinate or miss steps.
How We Built It
We started by mapping out every step a developer takes when handling
a vulnerability report manually. That became our initial flow — a
single sequential pipeline in flow.yml with one component per step.
v1.0 was that pipeline: scan, fix, test, create MR. It worked, but the context passing between steps was loose. Each step had to interpret what the previous one had done from free-text output, which meant the agent could misread its own prior work.
That limitation pushed us toward v1.1 — the multi-agent architecture. We split the pipeline into three explicit agent groups and introduced structured JSON contracts between them:
Scanner Agent → scanner_report JSON → Fixer Agent
Fixer Agent → fixer_report JSON → Validator Agent
Each agent now knows exactly what the previous one did. The Fixer Agent does not guess which packages need updating — it reads a typed list. The Validator Agent does not guess which files were changed — it reads a precise record.
We then built a real test application — taskflow-api, a Node.js
Express API — with genuine vulnerabilities embedded in the source code
and no hints in the README. The agent had to find everything by
reading the code itself. It did. It found CVE-2021-23337 in
lodash@4.17.20 by detecting _.merge() calls in tasks.js, caught
the JWT algorithm confusion vulnerability in auth.js, and correctly
handled the jsonwebtoken v8→v9 major bump by patching
jwt.verify() with the new required algorithms option.
What We Learned
Toolset boundaries are as important as prompts. Early versions gave every component access to every tool. The result was steps overreaching — the scanner trying to commit changes, the fixer trying to create MRs. Restricting each agent to only the tools it needs for its specific job made the whole system dramatically more reliable and predictable.
Structured handoffs change everything. The single biggest quality
improvement between v1.0 and v1.1 was not the additional features —
it was replacing free-text context passing with JSON contracts. Once
the Fixer Agent was reading a typed scanner_report instead of
interpreting a paragraph of text, the accuracy of its decisions
improved noticeably.
The hard problem is not detection — it is what happens after. Every existing tool is good at finding vulnerabilities. The genuinely difficult part is handling the downstream consequences: code that breaks after a version bump, coding standards that must be respected, tests that must pass, and vulnerabilities that have no fix yet and need to be tracked rather than forgotten. That is where the real engineering work in this project lives.
Team context matters enormously. Adding .vulndep.yml — a simple
config file teams drop in their repo root — transformed the agent
from a generic tool into something that feels like it belongs to your
team. It reads your reviewer list, respects your max version bump
preference, uses your linter command. That small addition made a
significant difference in how useful the output felt.
Challenges We Faced
Staying within the allowed tools enum. GitLab Duo's schema
validator enforces a strict list of permitted tool names. Early
versions of our flow.yml used run_pipeline, find_files, and
create_issue_note — none of which are in the allowed list. We had
to map each intended capability to the correct supported tool
(run_command, list_repository_tree, grep) without losing
functionality.
DeterministicStepComponent requires tool_name. We initially
used this component type for the pipeline testing step, not realising
it requires an explicit tool_name field rather than a toolset.
The CI validator caught this and we switched to AgentComponent
which gave us the prompt-driven flexibility we needed anyway.
Making the agent edit code, not just config. Getting the agent
to reliably patch source files — rather than stopping at
package.json — required being very explicit in the code_patcher
prompt about the steps: read the file, find the usage with grep,
identify the breaking change, apply the minimal patch, commit.
Vague instructions produced vague results. Specific step-by-step
instructions produced the code edits we needed.
The major version bump problem. Vulnerabilities that only have a fix at a major version bump are the hardest case — the fix exists but applying it automatically is risky. We solved this by making it a first-class outcome: a separate draft MR gets created, flagged for human review, with the breaking changes and migration steps documented. The agent does the research; a human makes the final call.
Test execution in the Duo environment. The agent produced
excellent MR documentation but noted test results as recommendations
rather than confirmed output in some runs — run_command in the Duo
flow context runs against the repo but may not have the full runtime
environment available. This is a known current limitation of the
platform rather than the agent design, and the MR review step serves
as the final human validation gate.
Built With
- duo-agents
- gitlab
Log in or sign up for Devpost to join the conversation.