GitLab MR security review, end to end: from access model to verdict
A practical walkthrough of bringing automated security review to every merge request on a GitLab project — which access model to choose, how webhooks fit 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.io can handle all three for you, or you can keep the GitLab credentials local and let CI / relay / compute do the SCM work.
- Managed SaaS — a GitLab token with
apiscope lets CodeGuards register webhooks, fetch diffs, and post comments for you. - CI runner — the pipeline sends the diff bundle to CodeGuards and uses local CI credentials to publish the MR comment.
- Realtime relay or compute instance — customer-hosted Docker services receive GitLab webhooks and keep SCM credentials inside your infrastructure.
Setup: under an hour, mostly waiting
The managed flow is intentionally boring. Connect the GitLab instance with a PAT, pick the projects you want covered, and CodeGuards.io takes care of the webhook and the first scan. Self-hosted GitLab works the same way — you just paste the hostname.
If your policy forbids vendor-held SCM tokens, use the self-hosted setup: CI runner for the minimum footprint, relay for realtime replies, or compute instance for one long-running local access plane.
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.io 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.