Skip to main content
Integration · GitHub Actions

GitHub Actions security scanning that can hold the merge

A step in your workflow file takes two things: the target to assess, and the severity at or above which it should fail. A finding at or above that line exits non-zero, which fails the job, which fails the check — and a check can be required on a protected branch. Everything under the line is still written up. It just does not stop you.

The gate

What the threshold does to the check

The threshold is yours to set. Everything else here follows from it mechanically, which is the point of writing it out rather than leaving it to be discovered on a red pull request.

What the threshold does to the check
StateWhat it meansWhat follows
The run is still in flight The job is queued or running. GitHub holds the check open, and a required check that is open is a merge that waits. No verdict yet.
Nothing at or above your threshold Terminal Findings under the line are still confirmed, still written up with the exchange that proved them, and still land in your dashboard. They do not decide the check. The step exits zero. The check passes.
One finding at or above your threshold Terminal A threshold compares against the highest severity the run confirmed, so one finding on the line is enough to decide it. The step exits non-zero. The job fails, and the check fails with it.
The same finding, after a retest A finding closes when a retest cannot reproduce it. Once it is closed it is no longer at or above anything, and the next run’s check is decided by what that run returns. It no longer holds your check red.
Key
  • The check is open and a required check holds the merge
  • Reported, and under the line you set
  • At or above the line you set
  • Closed on a retest that could not reproduce it
  • TerminalNo state follows this one

Branch protection

Making it a required status check, and what that costs you

GitHub names a status check after the job that produced it, so the job you wrap this step in is the string a branch protection rule asks for under its required-checks setting. Once it is required, a pull request will not merge while that check is failing — and it will not merge while the check is still pending either, which is the part to think through before you wire it. An assessment is not a unit test. In the fully autonomous model the observed median from scope sign-off to report is one to three business days, and that is an observation rather than a service level. So the useful question is not whether the check can be required. It is what you attach it to: the pull request itself, the push to the branch you release from, or a schedule that no commit triggers.

Where to put the line

Where you put the line, and what each choice costs you

The threshold is compared against the severity a finding carries, so the places you can put it are the severity bands themselves. It is a customer setting rather than a product default — this table is about the consequence of each choice, not about which one we prefer.

ThresholdWhat turns the check redWhat that choice is for
Critical only A finding scored critical. Everything under it is reported and the check still passes. A trunk that merges and ships on the same day, where the gate exists for the one class of defect nobody argues about.
High and above A finding scored high or critical. A release branch, where a high-severity defect is not something to carry into production and argue about afterwards.
Severity carries a CVSS v4.0 vector and the CWE, so the score your threshold is compared against is one your own team can recompute against its environmental metrics instead of taking ours.
Medium and above A finding scored medium, high or critical. A service that is not exposed yet, or a branch where you want to watch the gate bite before it is protecting anything that matters.
Any severity Any finding the run confirms, whatever it scored. A deliberate experiment to see the full shape of what a run returns. It is a loud setting, and it is not one to leave in place on a branch people are trying to merge into.

Critical only

What turns the check red
A finding scored critical. Everything under it is reported and the check still passes.
What that choice is for
A trunk that merges and ships on the same day, where the gate exists for the one class of defect nobody argues about.

High and above

What turns the check red
A finding scored high or critical.
What that choice is for
A release branch, where a high-severity defect is not something to carry into production and argue about afterwards.

Severity carries a CVSS v4.0 vector and the CWE, so the score your threshold is compared against is one your own team can recompute against its environmental metrics instead of taking ours.

Medium and above

What turns the check red
A finding scored medium, high or critical.
What that choice is for
A service that is not exposed yet, or a branch where you want to watch the gate bite before it is protecting anything that matters.

Any severity

What turns the check red
Any finding the run confirms, whatever it scored.
What that choice is for
A deliberate experiment to see the full shape of what a run returns. It is a loud setting, and it is not one to leave in place on a branch people are trying to merge into.

The step

What goes in .github/workflows, stanza by stanza

Read top to bottom, because the first stanza decides more about how this behaves than the step itself does.

The inputs

Two inputs and one secret

Names are written here as shape rather than as something to copy — what matters is that the step is given a target, a line, and a credential it does not hold itself.

01 Input

The target

What you set
The application, API or host this scan is against. One scan is one application or target, so a monorepo that ships two deployable services is two targets rather than one.
What it decides
Which coverage class the run is, and therefore what it actually does. A web application, a mobile app, an API, a thick client and a source tree are each worked as their own class.
What it never decides
Scope. The authorised scope is agreed in writing before a run, and nothing in a workflow file widens it.
02 Input

The severity threshold

What you set
The severity at or above which the step exits non-zero. It is the only value on this page that decides whether a build lives or dies.
What it does not change
What gets tested, and what gets reported. A finding under the line arrives in full, with the request, the response and the steps that reproduce it. It simply does not fail the job.
When to move it
Tightening it later is a one-line change in the workflow file, which is why starting at a line you can hold beats starting at the strictest one and quietly making the check optional afterwards.
03 Secret

The credential

Where it lives
In GitHub Actions secrets, read into the environment of that step alone.
What GitHub does with it
A workflow triggered by a pull request from a fork does not receive repository secrets. That is GitHub’s behaviour rather than ours, and it is worth knowing before you attach the gate to pull requests from forks.
What it is scoped to
The targets your organisation has authorised. A credential in a workflow file cannot reach a target the scope does not name.

What lands where

The check is the summary, not the deliverable

A red check tells a reviewer that something at or above the line exists. Everything that makes it actionable lands elsewhere, and arrives as it is confirmed rather than at the end.

Finding states

What has to happen before a finding stops failing your build

Four states, and only one of them is reached by doing something to the finding rather than by saying something about it.

What has to happen before a finding stops failing your build
StateWhat it meansWhat follows
Open Confirmed and reported, carrying the exchange that proved it. If it is at or above your threshold, this is the state holding the check red. The gate stays shut.
Fixed Your team has changed something and said so. The claim is recorded against the finding; it is not yet evidence. Waiting on a retest.
Retested The finding has been worked again, with the steps that produced it the first time. Either it reproduces, or it does not.
Closed Terminal The retest could not reproduce it. That is what closes a finding here — not a comment on a ticket, and not a line in a release note. It stops deciding your build.
Key
  • Decides the check while it is at or above your threshold
  • A claim recorded, awaiting evidence
  • Evidence being gathered
  • Terminal, and reached only by retest
  • TerminalNo state follows this one

The boundary

What the step is, and what it is not

The step is configuration in your repository and a credential in your secrets. The assessment itself runs on our side, which is why nothing has to be deployed inside your network for external and application testing — a deployment is needed only for internal network testing, and both on-premise and customer-VPC deployments are available for that. A GitHub-hosted runner is enough; a self-hosted runner works the same way and needs outbound access to reach us rather than anything inbound. Where the assessment data sits is your choice: India, the European Union, the United States, or Singapore and the wider Asia-Pacific region. And a pipeline is one way to trigger a run rather than the only one — the same assessment runs on a cadence you set, against the same target, with no commit involved at all.

Before the first run

What a gated pipeline is agreed against

The workflow file is the trigger. It is not the authorisation, and the two are kept apart on purpose.

What the step needs from you, and what is never in scope As of 2026-09-14
  • A target you are authorised to have tested, and a scope agreed in writing before the first run. A commit does not widen a scope.
  • A credential held in GitHub Actions secrets, and a decision about whether pull requests from forks are part of this at all.
  • A threshold. Yours to set, yours to change, and changing it changes what fails the job rather than what is tested or reported.
  • A decision about what the check is attached to — the pull request, the push to the branch you release from, or a cadence of your own.
  • The delivery model, which decides whose signature the report carries rather than what gets tested. All three cover the same coverage classes.

Deliberately excluded

  • Physical, hardware and wireless testing, which no coverage class on the platform covers.
  • Denial of service, which is never run against a production system.
  • Anything outside the authorised scope, whatever a workflow file asks for.

Gate one repository first, from $500

One scan is one application or target, and the entry tier is $500. A paid trial is $299 — one scan, one target — on the self-serve card flow, which reaches web applications, mobile apps, APIs, thick clients and secure code review. Anything wider than that is a scoping call.