After eight sessions of adding scanners to our GitLab pipeline — SAST, secret detection, SCA, license compliance, container scanning, DAST — we now have a different problem. We have hundreds of vulnerability findings. In Part 9, Patrick Steger and I look at GitLab’s built-in Vulnerability Management: what it gives you, where it falls short, and how to actually triage findings without losing your mind.
What GitLab’s Vulnerability Management Gives You#
GitLab Ultimate ships a Vulnerability Management module that aggregates findings from every scanner we wired into the pipeline. There are two main views. The security dashboard shows the number of vulnerabilities and how they develop over time. The vulnerability report lets you view and track individual findings.
One important detail: both views always reflect your default branch. In our project that is master. Vulnerabilities found on feature branches are not what you see here — only what hits the default branch counts.
In our sample Java project the report lists 165 vulnerabilities: 35 critical, 70 high, 48 medium, 11 low, and one unknown. You can filter by tool (SAST, dependency scanning, DAST, container scanning, secret detection) and by severity. Open a finding and you get an extended description — for example, a container scanning finding pointing at an SSL library, or a critical secret detection finding showing the exact line where an AWS access token landed in the source code.
From a finding you can do three things that matter: create an issue in GitLab’s issue tracker, mark it as fixed, or dismiss it. When you re-run the pipeline after a fix, GitLab even detects findings that are no longer reported by the scanners and marks them as potentially fixed, which you can then confirm as resolved.
The Limitations You Need to Plan Around#
The tool is a good starting point, but Patrick lists the limitations clearly, and they matter for any serious security process.
No comment on dismissals. When you dismiss a finding, you cannot say why. Six months later nobody knows whether it was a false positive, an accepted risk, or a misclick.
No time-boxed dismissal. You cannot say “accept this risk for six months and re-review.” It is dismissed or it is not.
Dismissal does not distinguish between false positive and accepted risk. Both end up in the same bucket. From a governance perspective those are very different things.
Strict format for third-party tools. You can integrate other scanners, but they must produce reports in the format GitLab expects. We would prefer the Vulnerability Management to be flexible enough to adapt to multiple formats — instead it is the tools that have to adapt.
Manual entries are clumsy. When you want to add a finding from a manual penetration test, GitLab demands fields like a CVE ID and an identifier URL — fields that simply do not exist for a manual finding. The workaround is to enter dummy values, which then live forever in the report. The information you can attach is also very limited.
Findings drift when source code changes. Patrick saw cases where, after a code change, the existing finding no longer pointed to the right line.
Triage Workflow in Practice#
In the demo we walked through the typical lifecycle. Most findings start as Needs Triage. Investigate one: if it is real, set it to Confirmed, then create an issue from the finding so a developer picks it up. The issue and the vulnerability are linked both ways, so you can navigate from finding to issue and back. If it is not real, Dismiss it — and accept that you cannot record why.
For a manual entry we tried “cross-site scripting from a penetration test.” GitLab refused the submission until we filled in an identifier code and URL. We added placeholder values, submitted again, and then filtered the report by tool = “manually added” to see it.
After fixing a batch of findings — in our case, bumping Spring Boot from version 2 to 2.7.2 and updating the test framework accordingly — we re-ran the pipeline. Filtering the vulnerability report by activity = no longer detected showed a long list of issues the scanners no longer find. We bulk-set them to Resolved. Same shortcoming as dismissal: no place to record context.
Creating an issue is also the practical workaround for the missing comment field. The issue gives you a place to write down what you actually did, which the vulnerability record itself does not offer.
Key Takeaways#
Vulnerability Management is part of the pipeline, not an afterthought. Once you have five or six scanners running on every commit, you need a place to triage their output. Without it, the scanners produce noise that nobody acts on.
The default-branch-only view is a real constraint. Findings on feature branches do not show up in the dashboard. Plan your branching model and your security gates around that.
Dismissal without a reason is a governance gap. False positive and accepted risk should not look the same. Until GitLab fixes this, use linked issues to record the reasoning.
Manual findings deserve a first-class workflow. The current path forces you to fake CVE IDs. If you do a lot of manual penetration testing, expect to keep a separate tool alongside GitLab.
Re-runs detect “no longer present” findings. Use the activity = no longer detected filter after every fix sweep — it is the fastest way to clean up the report and see real progress.
Issues are the workaround for everything the report cannot store. Comments, history, attachments, audit trail — all of that lives on the issue, not on the vulnerability. Build that habit early.
