GitLab MR security review, end to end: PAT to verdict in under an hour
A practical walkthrough of bringing automated security review to every merge request on a GitLab project — what permissions you actually need, how the webhook fits in, and the tuning that keeps the team's trust in week one.
What you actually need
To get useful security review on every merge request, you need three things glued together: an authenticated way to read the diff, a trigger that fires on every MR, and a place to put the verdict that the team will actually look at. CodeGuards handles all three, but it's worth understanding the moving parts before pasting anything in.
- A GitLab personal access token with
apiscope on the projects you care about. Group-level tokens work too if you want to scan a whole group at once. - A webhook registered on each project, so MR open / push events are picked up automatically. CodeGuards registers this for you when you connect the repo.
- An output channel: by default the verdict is posted on the MR thread and in the dashboard. CI integration is optional and only needed if you want to fail the pipeline on critical findings.
Setup: under an hour, mostly waiting
The actual flow is intentionally boring. Connect the GitLab instance with the PAT, pick the projects you want covered, and CodeGuards takes care of the webhook and the first scan. Self-hosted GitLab works exactly the same — you just paste the hostname.
For a typical 30-repo setup the only real cost is deciding which severity bands you want surfaced loudly. Start strict (CRITICAL + HIGH on the MR thread, MEDIUM in the dashboard only) and loosen later — it's easier to dial up signal than to convince the team to stop ignoring a noisy bot.
Week one: tuning instead of muting
The first week is about deciding what your repos actually consider risky. Some examples that come up almost every time:
- A test fixture that loads a real-looking secret from
.env.example— fine for that repo, but every fresh scanner flags it. - A legacy admin endpoint that uses
$request->all()for mass assignment behind a hard auth boundary — risky pattern, accepted compensating control. - A logger that prints raw URLs to stdout in development mode only.
Each of these is a one-click "accepted, internal policy covers it" with a short note. CodeGuards scopes the suppression to that repository and remembers the reason, so the next MR doesn't reopen the same conversation. Crucially, the same pattern in another repo still gets reported — your tolerance for one codebase doesn't lower the bar everywhere.
Optional: blocking on CI
The webhook + thread comment is usually enough. If you want CI to actually fail the pipeline on critical findings, drop a single curl into .gitlab-ci.yml as an MR-only job. That gives you the loud blocker for the cases you really care about, without forcing every project to add a new pipeline stage.
Most teams turn this on after a couple of weeks, once the signal is dialed in and they trust the verdicts.