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
Share this project:

Updates