Skip to main content
Integration · GitLab CI

GitLab security scanning that runs as a pipeline stage

The job is configuration you add to .gitlab-ci.yml: a stage, a script step, and a variable holding the severity that fails it. What runs behind that job is a penetration test that proves what it reports — and the threshold deciding whether your deploy stage starts is a line in your own file, not a setting we hold.

The job definition

What goes into .gitlab-ci.yml

Five keys. The values below are illustrative — the job name is yours, and every value in the middle column is a decision rather than a default.

KeyIllustrative valueWhat it decides
stage: security Which stage the job belongs to, declared in the top-level stages list like any other. Where you put it in that list is the whole design decision: it fixes what has already run when the scan starts, and what is downstream of the result.
script: An authenticated call to the B-52 API Starts the run against the target this job names, and executes on whichever runner the stage already uses. How the job then relates to the result — whether it waits, or hands off to something later in the pipeline — is settled when the integration is set up, against how long your runs take and what your runner timeouts allow.
variables: B52_FAIL_ON: high The severity threshold — the one line here that can stop a pipeline. It is read against the finished run, so a finding at or above it fails the job and a finding below it is reported without gating anything.
The variable name is illustrative. What is not illustrative is where it lives: in the file you commit, so changing the bar is a merge request rather than a support ticket.
rules: if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH Which pipelines carry the job at all — every push, the default branch only, merge requests, or a schedule. A scheduled pipeline is a pipeline, so the cadence question is answered here too.
allow_failure: false Whether a failed job stops the pipeline or is recorded and stepped over. This is the line that decides whether the gate bites or advises, and it is a one-word change in either direction.

stage:

Illustrative value
security
What it decides
Which stage the job belongs to, declared in the top-level stages list like any other. Where you put it in that list is the whole design decision: it fixes what has already run when the scan starts, and what is downstream of the result.

script:

Illustrative value
An authenticated call to the B-52 API
What it decides
Starts the run against the target this job names, and executes on whichever runner the stage already uses. How the job then relates to the result — whether it waits, or hands off to something later in the pipeline — is settled when the integration is set up, against how long your runs take and what your runner timeouts allow.

variables:

Illustrative value
B52_FAIL_ON: high
What it decides
The severity threshold — the one line here that can stop a pipeline. It is read against the finished run, so a finding at or above it fails the job and a finding below it is reported without gating anything.

The variable name is illustrative. What is not illustrative is where it lives: in the file you commit, so changing the bar is a merge request rather than a support ticket.

rules:

Illustrative value
if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
What it decides
Which pipelines carry the job at all — every push, the default branch only, merge requests, or a schedule. A scheduled pipeline is a pipeline, so the cadence question is answered here too.

allow_failure:

Illustrative value
false
What it decides
Whether a failed job stops the pipeline or is recorded and stepped over. This is the line that decides whether the gate bites or advises, and it is a one-word change in either direction.

Where it sits

The same job, read in three pipelines

The job definition does not change between these. What changes is what a failure costs, and that is decided by where the job sits rather than by anything we control.

01 Merge request

Before the branch is merged

What triggers it
A merge request pipeline, running against the branch as it would be merged.
Where the job sits
After build and test, ahead of anything that moves the change somewhere. The stages behind it are the ones that would act on it.
What a failure costs
The merge, and nothing else. Nothing has shipped yet, which makes this the cheapest place on the list for the gate to bite.
02 Default branch

After the merge, before the deploy

What triggers it
A push to the default branch, which is the pipeline the deploy stage hangs off.
Where the job sits
In its own stage between build and deploy. Every job in the deploy stage and behind it is waiting on this one to finish.
What a failure costs
The deploy. With allow_failure set to false the deploy stage never starts, the pipeline ends failed, and the job output names the finding that did it.
03 Schedule

On a cadence, against what is already live

What triggers it
A scheduled pipeline, at whatever interval the project sets.
Where the job sits
Usually alone. There is no build in front of it and nothing queued behind it.
What a failure costs
Nothing downstream, because there is nothing downstream. The red job is the signal, and the findings are in the dashboard either way.

What it takes from you

A target, a credential and one job

Three things, and two of them are already GitLab’s. The target is the application or host this job is testing, named in the job. The credential is a project CI/CD variable, masked and protected the way the rest of your pipeline secrets are, so it never appears in the file you commit. The job itself is the block above, reviewed in a merge request like any other change to the pipeline. Nothing is required inside your network for external and application testing — the runner calls out, and the run happens on our side, in the data residency region you choose. Internal network testing is the one case that does need something deployed on the inside, and on-premise and customer-VPC deployments both exist for that.

The gate

What a failed job does to the stages behind it

Four outcomes. Which one you get is settled by two lines you already wrote — the threshold, and allow_failure.

What a failed job does to the stages behind it
StateWhat it meansWhat follows
Nothing at or above your threshold The run finished and everything it returned sits below the severity you set. Job passes. The next stage starts.
A finding below your threshold Reported, and not a gate decision. It lands with the same evidence as anything else — the threshold governs the pipeline, not what gets found or written up. Job passes. The finding is still there to work.
At or above your threshold, with allow_failure false Terminal The gate bites, and GitLab treats it as it treats any failed job in the stage. Job fails. Nothing in a later stage starts, and the pipeline ends failed.
At or above your threshold, with allow_failure true The same result, recorded instead of enforced. This is the setting to run while you are deciding where the bar belongs. Job fails and is marked. The pipeline carries on to the next stage.
Key
  • The pipeline carries on
  • Reported, not gated
  • Recorded, and the pipeline carries on
  • The pipeline stops at this stage
  • TerminalNo state follows this one

The threshold

One variable, reviewed like any other line

The severity that fails the build is a variable in the file you commit, and that placement does three things a console setting cannot. It changes through a merge request, so raising or lowering the bar is reviewed by the people who own the pipeline. It is versioned, so the history of what you gated on sits in git beside the history of what shipped. And it can differ between branches, or between projects, without anybody asking us. One case is worth writing down because it is not a severity decision at all: if the run ends without a result — a target that never answered, a credential that expired — there is nothing to compare against the threshold, and the job fails the way any job fails. That is an infrastructure failure, the job output says so, and it should be read as one.

Per finding

What the job hands back

The job output names what crossed the line. Everything beneath it is already in the dashboard while the pipeline is still going red.

In the job output

The finding that crossed the threshold

Its severity, the target it was found on, and why the job exited as it did — so the engineer looking at a red pipeline does not have to open anything else to know what happened.

Always

The traffic that proved it

The request as sent and the response as returned, with the part that proves the defect marked. Findings are reached rather than inferred from a version banner, which is what makes the gate safe to enforce.

Always

Steps that reproduce it

Written for the engineers who own the code, to be followed without a call back to us first. A finding nobody can reproduce is a ticket that stays open.

Always

Severity, with its vector

A CVSS v4.0 vector and the CWE travel with every finding, so the score the threshold is read against can be recomputed under your own environmental metrics rather than inherited from us — which matters most while you are still settling where the bar belongs.

Over time

The state the finding is in

Open, fixed, retested, closed. It closes on a retest that cannot reproduce it, so a pipeline that stopped once has a documented reason it stops no longer.

Where it lands

Four places, and the question each one answers

WhereWhat is thereWho reads it
The job output The finding that crossed the threshold, its severity, and the target it was found on. The engineer whose pipeline just went red.
The pipeline view A failed stage, with everything behind it showing as never started. On a merge request pipeline it sits against the change that caused it. Whoever is reviewing the merge request.
The dashboard Every finding from the run — proved, classified, and carrying its state — including the ones below the threshold that stopped nothing. The team that has to fix them.
The delivery models The written report, and whose signature it carries. That is decided by the delivery model you chose, not by which pipeline the run started from. Whoever has to file it.

The job output

What is there
The finding that crossed the threshold, its severity, and the target it was found on.
Who reads it
The engineer whose pipeline just went red.

The pipeline view

What is there
A failed stage, with everything behind it showing as never started. On a merge request pipeline it sits against the change that caused it.
Who reads it
Whoever is reviewing the merge request.

The dashboard

What is there
Every finding from the run — proved, classified, and carrying its state — including the ones below the threshold that stopped nothing.
Who reads it
The team that has to fix them.

The delivery models

What is there
The written report, and whose signature it carries. That is decided by the delivery model you chose, not by which pipeline the run started from.
Who reads it
Whoever has to file it.

The boundary

What this job is, and what it is not

The GitLab CI integration as it stands As of 2026-09-14
  • A job you add to your own .gitlab-ci.yml: a stage, a script step that calls the B-52 API, and a variable holding the severity threshold. It is configuration in your repository, reviewed the way the rest of your pipeline is reviewed.
  • The run behind the job is the same six-phase engagement as any other — discovery, planning, scanning, exploitation, reporting, and QA sixth, running on the finished report before it reaches you.
  • The threshold is yours and it is the only thing that turns a result into a pipeline decision. GitLab’s own allow_failure decides whether that decision is enforced or merely recorded.
  • One scan is one application or target. A pipeline that builds several applications needs a job per target, and each target is scoped before its first run rather than inferred from the repository.
  • Nothing is required inside your network for external and application testing. Data residency is your choice of India, the European Union, the United States, or Singapore and Asia-Pacific.

Deliberately excluded

  • Physical, hardware and wireless testing. No coverage class on the platform carries them, and adding a job cannot ask for one that does.
  • Denial of service. It is not run against a production system, and a pipeline is usually pointed at one.
  • Anything outside the scope you signed off. Adding a job to a pipeline does not widen that scope; the target the job names has to be in it already.

Getting it running

One target, one job, one threshold

One scan is one application or target, and the entry price is $500. A pipeline that builds one application needs one job. A pipeline that builds several needs a job for each, named and scoped before the first run. Where the target is a web application, a mobile app, an API, a thick client, or a secure code review, the whole thing can be set up on the card flow without speaking to anyone. Everything else — a perimeter, an internal network, a cloud estate, an Active Directory, an LLM application — starts with a scoping call, because what is in scope gets signed rather than typed into a form. Beyond a single target the arrangement is settled on three axes — how many targets, how often they run, and which delivery model signs the report — and those are a conversation.

Put it in one pipeline, from $500

One scan is one application or target. A $299 trial is one scan on one target through the card flow — open to web applications, mobile apps, APIs, thick clients and secure code review — enough to see exactly what the job hands back before you wire it into the branch that matters.