To connect GitLab to Safeguard, create a GitLab group, project or personal access token with the read_api and read_repository scopes, paste it into the GitLab card under Integrations, pick your projects and a branch or tag, and connect. Safeguard then clones and scans each project for an SBOM and findings. Add the api scope and the Developer role only if you want Safeguard to open fix merge requests, comment on merge requests and set a quality gate commit status. Self-managed GitLab works too, as long as the server has a public internet address.
Below: token choices, the exact steps in GitLab and Safeguard, merge request checks built from a CI job and a policy gate, fix merge requests, and the limits that decide whether you connect directly or scan from your own runners.
Key takeaways
- The GitLab source code integration is part of ESSCM, Safeguard's Enterprise Software Supply Chain Manager (GA). It connects GitLab.com or a self-managed GitLab server that is reachable over a public address.
- SCA, the dependency and SBOM scan that runs on every connected project, is GA. SAST runs as part of the default CI scan and is generally available.
- For scanning only, the least privilege setup is a group access token with the Reporter role and the
read_apiandread_repositoryscopes. Nothing in GitLab needs write access unless you use the write features. - Fix merge requests come from autonomous remediation (GA, ships switched off with every category set to Manual). A person reviews and merges every fix merge request in the setup this post recommends.
- Merge request checks come from two GA features working together: the
safeguard ci scanstep in GitLab CI/CD (the CLI is GA) and policies and gates (GA) with Block, Warn and Notify actions. - Scan modes on connect are fast and deep (GA) and progressive (GA).
- There are no GitLab webhooks to configure. New commits are picked up by a CI job, a schedule or a manual rescan.
- Signing in with GitLab OAuth is not available; the integration takes an access token. Every label here matches our capability map.
Why connecting your source control is a security decision, not a convenience
Grant a scanner whatever it asks for and you end up with a long lived credential that can write to every repository in the company. The standards that govern secure development take this seriously. NIST SP 800-218, the Secure Software Development Framework (SSDF) version 1.1, published in February 2022, has a practice for exactly this. PS.1.1 asks organisations to store all forms of code based on the principle of least privilege, so that only authorised personnel, tools and services have access. A scanner is one of those tools. PO.3.2 asks you to follow recommended security practices to deploy, operate and maintain tools and toolchains, which covers how a scanner is configured and how its credentials are managed.
The OWASP Top 10 CI/CD Security Risks names the same failures from the attacker's side: CICD-SEC-2, Inadequate Identity and Access Management, and CICD-SEC-6, Insufficient Credential Hygiene, cover machine identities with too much access and tokens that live too long. CICD-SEC-1, Insufficient Flow Control Mechanisms, covers code that lands without review, which matters once a tool can open its own changes. CICD-SEC-3, Dependency Chain Abuse, is what the scan addresses.
So decide three things: what the scanner can read (only the projects you choose), what it can write (nothing until you want fix merge requests, and never a direct push to a protected branch), and how changes land (through your existing merge request approval rules, with a human approving). Safeguard's GitLab guide is written around those questions: every scope and role, which are optional, and what Safeguard never needs.
What the GitLab integration does
The GitLab card connects Safeguard to projects on GitLab.com or on your own self-managed GitLab server. According to Safeguard's GitLab integration guide, Safeguard lists the projects a token is a member of, and each project you select, at a branch or tag, becomes a Safeguard project that is cloned and scanned for an SBOM and findings. With write access, Safeguard can also open fix and upgrade merge requests, comment on merge requests and set a commit status for the quality gate. You find the card under Integrations on the Project/Product tab.
There are two connection styles.
| Public | Private | |
|---|---|---|
| What you enter | One or more public project URLs, such as https://gitlab.com/group/repository-name | Where GitLab runs, and an access token |
| Credential needed | None | A personal, group or project access token |
| What you can scan | The public projects you add by URL | Every project the token is a member of |
| Container registry | The registries of the public projects you added, pulled without a token, if the registry is public too | The registries of every project the token can read, pulled with the token |
Each selected project then behaves like any other ESSCM project: an SBOM, dependency findings from SCA (GA), prioritisation with reachability, EPSS and KEV (GA), license data from license compliance (GA), and policy evaluation. Those pieces each have their own deep dives, so this post stays on the GitLab side. If you want the prioritisation logic, read how Safeguard ranks findings with reachability, EPSS and KEV.
GitLab.com versus self-managed GitLab
The docs say the integration supports both. On the Private tab, under "Where does GitLab run?", you keep GitLab.com (SaaS) or choose Self-managed and enter your server URL, such as https://gitlab.example.com.
There is one hard condition for self-managed servers. The guide states that a self-managed server must be reachable from the internet over a public address, and that Safeguard does not send a token to private or internal addresses. If you enter a private, internal or credential bearing address, you get the error "This server address isn't allowed". That is a deliberate guard against a token being sent somewhere it should not go, and it means a GitLab server that lives only on a private network cannot be connected through the card.
For that case the CI/CD docs offer a different route (for the wider choice, see repo connection, CLI or local runner). safeguard ci scan --local scans on the runner, in a checkout inside the job, and only the results leave the runner. The CI/CD integration page says this mode works for self-hosted and firewalled Git servers. We cover that path in the walkthrough below. The trade is that the local mode produces the SBOM and dependency vulnerabilities, while the default mode on Safeguard also runs SAST (GA).
Choosing a token: group, project or personal
GitLab offers several kinds of token. Safeguard's guide says which ones work and which do not.
| Token type | Works with Safeguard? | Reach | Notes from the docs |
|---|---|---|---|
| Group access token | Yes, recommended | The group's projects and subgroups | Creating one needs the Owner role on the group. On GitLab.com it needs Premium or Ultimate |
| Project access token | Yes | One project | Creating one needs the Maintainer or Owner role on the project. On GitLab.com it needs Premium or Ultimate |
| Personal access token | Yes | Every project its user is a member of | The option on GitLab Free on GitLab.com, or when you want the token tied to a person or service account |
| Deploy token | No | Not applicable | GitLab does not accept deploy tokens on the REST API, so Safeguard cannot list projects with one |
| CI/CD job token | No | Not applicable | It exists only inside a pipeline job |
| OAuth sign in | Not available yet | Not applicable | The dialog takes an access token only |
GitLab's own documentation backs up the vendor side of this. GitLab's group access token page says that on GitLab.com, group access tokens require a Premium or Ultimate subscription, and that you need the Owner role for the group to create one. GitLab's deploy token page says deploy tokens cannot be used with the GitLab public API, which is why Safeguard cannot use them to list projects.
A group or project access token creates a bot user with the role you pick: clearly a machine, not tied to an employee, limited to the group or project. A personal access token sees every project its user is a member of, so on GitLab Free create it for a dedicated service account with membership only in the projects you want scanned.
Scopes Safeguard needs
| Scope | Required? | What Safeguard uses it for |
|---|---|---|
read_api | Required | Checking the token, listing projects you are a member of, and reading project details, branches, tags and files |
read_repository | Required | Cloning the project over HTTPS for the scan |
read_registry | Optional | Pulling the container images you choose in the Container registry section for image scans |
api | Optional | Fix and upgrade merge requests, merge request comments and discussions, the quality gate commit status, and merging from Safeguard |
write_repository | Not needed | It covers Git push only and does not work with the API; Safeguard writes through the API |
read_user | Not needed | read_api already covers the user check |
A token with only read_repository cannot call the API and fails the check, so you need both read scopes. api is broad: it includes everything read_api grants plus write access, so add it only when you want the write features.
Roles the token needs
| Role | Required? | What it allows |
|---|---|---|
| Reporter | Required | Reading and cloning the code of private projects |
| Developer | Optional | Creating branches and merge requests for fix merge requests, and the quality gate's commit status |
| Maintainer | Optional | Only if Safeguard should merge into a protected branch |
The docs add that on GitLab 18.7 and later, the Planner role can also read private repository content. A personal access token acts with the role its user has in each project, so that user needs at least Reporter.
The docs also list what Safeguard never needs: sudo, admin_mode, write_registry, create_runner, k8s_proxy, the Owner role, or any write scope when you only scan. If a setup guide from anywhere asks a scanner for those, treat it as a red flag.
The least privilege answer
Safeguard's guide states it plainly: a group access token with the Reporter role and the read_api and read_repository scopes. Start there. Upgrade to Developer and api later, for one group at a time, when you turn on fix merge requests. Do not grant Maintainer. The setup in this post keeps merging in human hands, and withholding Maintainer is the simplest way to make sure Safeguard cannot merge into a protected branch.
Token expiry
GitLab sets an expiry date on every token. GitLab's personal access token page says that if you do not enter a date, the expiry is set to 365 days from today, and by default it cannot be more than 365 days out. Safeguard's guide recommends choosing something shorter, such as 90 days. When a token expires, listing and scans fail until you paste a new token into the connection. GitLab emails the project or group members before a bot token expires, so make sure those emails reach someone who owns the integration.
Step by step: connect a private GitLab group to Safeguard
This connects a GitLab.com group with the least privilege token, then shows the self-managed changes. Steps come from Safeguard's GitLab guide and GitLab's token docs.
Step 1: Create a group access token in GitLab
- In GitLab, select Search or go to, and open your group.
- Select Settings › Access tokens, then Add new token.
- Enter a name, such as
safeguard-scan, and an expiry date. Ninety days is a sensible default. - Choose the Reporter role. Choose Developer only if you already know you want fix merge requests for this group.
- Select the scopes
read_apiandread_repository. Addapionly for fix merge requests. - Select Create group access token and copy the token. GitLab shows it only once.
A project access token is the same flow from the project's settings, and reaches only that project.
If you prefer the command line, Safeguard's guide shows the GitLab CLI form. These commands are copied from the docs; we did not run them for this post.
# Group access token, Reporter role, read-only, 90 days
glab token create --group my-group --access-level reporter \
--scope read_api --scope read_repository --duration 90d safeguard-scan
# Project access token for one project
glab token create --repo my-group/my-project --access-level reporter \
--scope read_api --scope read_repository --duration 90d safeguard-scan
# Check what the token sees, as Safeguard does
curl --header "PRIVATE-TOKEN: <token>" "https://gitlab.com/api/v4/projects?membership=true&per_page=100"
The last command lists the projects the token is a member of, which is the list Safeguard will show you. If a project is missing, fix the membership in GitLab first.
Step 1 (alternative): Create a personal access token
If you are on GitLab Free on GitLab.com, or you want a token tied to a service account:
- Select your avatar, then Edit profile.
- Select Access › Personal access tokens, and from the Generate token dropdown choose Legacy token.
- Enter a token name, set an expiration date, and select
read_apiandread_repository. Addapionly for fix merge requests. - Select Generate token and copy it. GitLab shows it only once.
Step 2: Connect the token in Safeguard
- In Safeguard, open Integrations and choose the Project/Product tab.
- Click Connect on the GitLab card.
- Choose Private and enter a Name for the connection, plus an optional Description.
- Under "Where does GitLab run?", keep GitLab.com (SaaS).
- Paste the token into Access Token and click Check. "Access token validated successfully" means GitLab accepted it.
- Click Next, select the projects and the branch or tag for each, and finish with the Connect button, choosing a fast, deep or progressive scan.
Each project you selected becomes a Safeguard project and is scanned when you connect it.
Which scan mode to pick on connect
Fast and deep are GA; progressive is generally available, so try it on a few projects before relying on it. A practical start is fast to confirm the setup works, then deep on the projects you ship.
Step 3: For self-managed GitLab, change two things
- Under "Where does GitLab run?", choose Self-managed and enter your server URL, such as
https://gitlab.example.com. - Create the token in your own GitLab instance, checking your edition and token policy.
The server must be reachable over a public address; for an internal only GitLab, use the CI route with --local.
Step 4: Add container images from the GitLab registry (optional)
The guide says container image scanning from the GitLab registry is switched on per workspace. When it is on:
- Open the GitLab connection again with Update.
- On the review step, the Container registry section lists the images in your projects' GitLab container registries, read with the same server and token.
- Tick an image, pick a tag, and save.
Each image becomes its own project, scanned with the same token and pinned to the digest the tag points at when the scan starts. Pulling images needs read_registry; listing images and tags uses read_api. Image layers are covered by SCA (GA). For a deeper look at image projects per environment, see tracking container images per environment in Safeguard.
Step 5: Decide how new commits get scanned
The guide says the GitLab integration has no webhooks to set up. Each selected project is scanned when you connect it. To rescan new commits, you have three options:
- A schedule. Set one under Integrations › Scheduled.
- A manual rescan. Choose Rescan from the connection's menu under Project Configurations.
- A CI job. Add
safeguard ci scanto.gitlab-ci.ymlso every push and merge request is scanned. This is the route that gives you merge request checks, and it is the next section.
Container registry images are rescanned on a schedule too.
What Safeguard reads and stores
Read: the token's user, the list of projects it is a member of with their details, branches and tags, and the contents of each selected project at the chosen branch or tag. For merge request features, the merge request, its changes and its notes. With the container registry on, the projects' registry repositories and tags, and the images you choose.
Stored: the connection's settings, the projects you selected, the SBOM and findings of each scan, and the token. The token is encrypted at rest with a per workspace key and never shown again after you save it.
Not stored: source code. The scanner makes a shallow clone into a temporary folder and deletes it when the scan ends.
The guide also states that no customer data from this integration is sent to third party LLMs.Record which groups the token reaches alongside this list; together they are good evidence for SSDF PS.1.1.
Merge request checks: scan every MR in GitLab CI/CD
The source code integration scans what you connected. Merge request checks come from GitLab CI/CD. Safeguard's CI/CD docs describe one step in your pipeline that runs safeguard ci scan. Each push becomes a new version of its Safeguard project, named after the branch and commit (for example main-a1b2c3d), and the step can fail the job when the scan finds vulnerabilities at or above a severity you choose. On a merge request, the source branch is scanned.
The docs say there is no plugin, Action or Pipe to install. Every CI system runs the CLI (GA) in a plain shell step, on a Linux or macOS runner with bash and curl.
Step 1: Create a secret key in Safeguard
Create a secret key under Settings › Developer with the scopes scans:trigger and scans:read. The CI/CD overview says you can lock the key to one project. Do that when one key serves one pipeline; it limits what a leaked key can do.
Step 2: Store it as a masked CI/CD variable in GitLab
In GitLab, add the key under Settings › CI/CD › Variables as SAFEGUARD_API_KEY, with Mask variable on. Leave Protect variable off if unprotected branches or merge requests should be scanned.
That second sentence matters. GitLab's CI/CD variables page says a protected variable is only available in pipelines that run on protected branches or tags. A merge request from a feature branch is not protected, so a protected SAFEGUARD_API_KEY is simply absent in that pipeline, and the Safeguard step fails with AUTHENTICATION_REQUIRED. Safeguard's GitLab CI page lists exactly that symptom and cause.
GitLab's docs also note that since GitLab 18.3 the default visibility for a new variable is Masked, with an option for Masked and hidden. Keep it masked either way.
The trade: an unprotected variable is readable by any pipeline in the project. Offset it by keeping the key to scans:trigger and scans:read, locking it to the project and rotating it with your GitLab token, which is what CICD-SEC-6 asks for.
Step 3: Add the job to .gitlab-ci.yml
Safeguard's docs offer an included template and a plain job. This post uses the plain job the docs show under "Without the template", because every line of it is visible in your own repository and nothing is pulled in from outside at pipeline time. The snippet is copied from the docs; we did not run it for this post.
safeguard:
stage: test
image: alpine:3.20
variables:
SAFEGUARD_INSTALL_DIR: /usr/local/bin
before_script:
- apk add --no-cache bash curl ca-certificates
- curl -fsSL https://cli.safeguard.sh/install | bash
script:
- safeguard ci scan --wait --fail-on critical
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
The job installs the CLI on Alpine, triggers the scan, waits, and fails on a critical dependency vulnerability; the rules run it on merge request pipelines and pushes to main. If your pipeline has no test stage, change stage, or the job will not appear.
The CI/CD overview recommends pinning a CLI version with SAFEGUARD_VERSION=<version> on the install line, so a CLI release does not change a build under you. Do that once the job is stable.
The app can also generate this setup for you. The CI/CD overview says the GitHub, GitLab, Bitbucket and Azure DevOps cards under Integrations › Source Control have a Set up CI/CD button that opens the setup with your branches and severity filled in, and a project's own Set up CI/CD button does the same with that project's id. The docs use two names for where the card sits (the Project/Product tab and Source Control); look for the GitLab card under Integrations either way.
Where the CI scan runs, and why it matters for self-managed GitLab
The CI/CD overview describes two modes.
| Mode | Command | Needs | Scans |
|---|---|---|---|
| On Safeguard (default) | safeguard ci scan | The repository connected as an integration, or public on gitlab.com | SAST (GA), SBOM, dependency vulnerabilities |
| On the runner | safeguard ci scan --local | A checkout in the job; no integration | SBOM, dependency vulnerabilities |
In the default mode, Safeguard clones the repository itself, which is why it needs the GitLab connection from the first half of this post. With --local, only the results leave the runner, so the docs say it also works for self-hosted and firewalled Git servers. It reads dependencies from lockfiles and manifests in the checkout, such as package-lock.json, go.sum, requirements.txt and pom.xml.
If your GitLab is self-managed and private, this is the route. The docs show --local as a flag on safeguard ci scan. Check safeguard ci scan --help on your CLI version for how it combines with --wait and the fail flags before you rely on it in a required job.
Turning the scan into a merge request check
GitLab shows the job's result on the merge request's pipeline. To make it a real check, require a successful pipeline before merging in your GitLab project settings; then a merge request that adds a critical dependency vulnerability cannot merge until it is fixed or excepted.
The fail flags the CI/CD overview documents:
| Flag | Fails the step when |
|---|---|
--wait | The scan fails or takes longer than --timeout (default 30m) |
--fail-on <severity> | With --wait: a dependency vulnerability at or above critical, high, medium or low |
--fail-on-gate <gate> | With --wait: a policy gate (its name, id, or all) is not Passed or Warning |
The docs add an important caveat: SAST findings are scanned and shown in Safeguard but are not part of --fail-on yet. So the severity threshold gates on dependency vulnerabilities only. Drop --fail-on to report without failing.
Gating on a policy instead of a severity
A severity threshold is blunt. For rules like "block a license we do not allow", use policies and gates (GA).
The policies and gates docs describe policies as rules that evaluate your SBOMs and generate findings, and gates as checkpoints that enforce policies. You create a policy under Settings › Policies with Create Policy, define its conditions in the visual builder or YAML, set its severity and action, and save. Conditions in the docs include vulnerability severity, CVSS score, EPSS score, license, component age and maintenance status.
Each policy has one Action that decides what a failed policy does to its gate:
| Action | When the policy fails |
|---|---|
| Block | The gate fails |
| Warn | The gate still passes, with a warning that names the policy |
| Notify | The gate still passes, and Safeguard sends a notification to your workspace's configured channels |
The policy outcomes page adds two rules worth knowing: an Error is never counted as a pass, and a gate that has never been evaluated shows Not evaluated, never Passed.
Then change the script line to use the gate:
script:
- safeguard ci scan --wait --fail-on-gate <gate name or id>
The policy outcomes page says this waits for the scan to finish and for the gate's policies to be evaluated on the new data, then exits 0 when the gate is Passed or Warning (printing warnings and notified policies), and exits non-zero when the gate is Failed (printing the Block policies that failed), Error, Not evaluated, or not evaluated before --timeout. --fail-on and --fail-on-gate can be combined.
One prerequisite: policy outcomes are turned on per workspace by Safeguard. If your workspace does not show an Outcome column on the Gates and Policies tables, ask your Safeguard contact to turn it on. For writing the policies themselves in plain language, see turning plain English security policies into release gates.
The commit status and MR comments from the integration
The integration guide also lists merge request comments and discussions and a quality gate commit status, using the integration's own token. Both need api, and the commit status needs Developer. They are optional; if the CI job is your check, leave the integration read only.
When a gate blocks: exceptions
The policies and gates docs describe exceptions: click Request Exception, select the policy and finding, give a business justification, set an expiration date and submit. Approvers decide, and the decision lands in an audit trail. Enforce the expiry: SSDF RV.2.2 asks you to plan and implement risk responses, and an exception that never expires is one nobody revisits.
Fix merge requests: how they are created and why a human merges them
Safeguard's remediation feature is called AI Remediate in the docs and autonomous remediation in the offering. It is GA and it ships switched off, with every category set to Manual. Nothing opens a merge request in your GitLab until someone turns it on and grants the write access it needs.
What a fix merge request contains
The AI Remediate docs describe the flow as analysis, solution generation, an impact assessment that checks breaking changes and compatibility, the pull request, and validation by running tests. On GitLab, that request is a merge request; the GitLab guide lists "fix and upgrade merge requests" as the write feature. For dependencies, the docs say Griffin can upgrade vulnerable packages to fixed versions, handle transitive dependency conflicts and update lock files.
The docs list what each generated request includes: a clear title with the CVE reference, a description of the vulnerability, a summary of the changes, breaking change warnings if any, test results if configured, and links to the relevant advisories.
Breaking change detection sorts each fix into four levels: Safe, Minor, Major and Critical. The docs' own best practice is to start with safe fixes, review major changes and not auto merge breaking changes.
The docs list full AI Remediate support for npm, yarn and pnpm, pip, poetry and pipenv, Maven and Gradle, Go modules, Cargo, Bundler, NuGet and Composer.
Three ways to start a fix
From the AI Remediate docs:
- Single vulnerability. Open the vulnerability, click AI Remediate, review the proposed changes, then click Create Pull Request. On a GitLab project, the result is a merge request.
- Bulk. Select vulnerabilities, click AI Remediate Selected, let Griffin batch compatible fixes, review all changes, and create the requests, one per repository.
- Page level. From the Vulnerabilities page, click AI Remediate at the top, review the remediation plan Griffin builds from all fixable vulnerabilities, and approve to create the requests.
The settings that keep a human in the loop
The AI Remediate settings live under Settings › AI Remediate. The docs list five:
| Setting | What it controls | Setting this post recommends |
|---|---|---|
| Auto-Create PRs | Automatically create requests for safe fixes | Off to start |
| Require Approval | Require manual approval before requests are created | On |
| Test Integration | Run tests before creating requests | On, if your project has tests |
| Breaking Change Policy | How breaking changes are handled | Never auto merge; route Major and Critical to review |
| Branch Naming | The naming convention for fix branches | Match your existing branch rules |
Then add GitLab's side. GitLab's merge request approvals docs say approvals can be optional or required, depending on your project's needs and GitLab tier, and that you can create required rules about the number and type of approvals. Put a required approval rule on the projects you connect. Keep the Safeguard token at Developer, not Maintainer, so Safeguard can open merge requests and branches but cannot merge into a protected branch.
Safeguard finds the safe version, edits the manifest and lockfile, runs tests and writes up the change; a person who owns the project decides. The fix branch's pipeline runs the same Safeguard job, so the fix passes the same gate as everything else.
For how the eight remediation categories and four strategies work, and why everything starts in Manual, see autonomous remediation in Safeguard.
Decision table: which setup fits your GitLab
| Your situation | Token | Scopes and role | How new commits are scanned | MR checks |
|---|---|---|---|---|
| GitLab.com, Premium or Ultimate, scan only | Group access token | read_api, read_repository, Reporter | CI job, schedule or manual rescan | CI job with --fail-on or --fail-on-gate |
| GitLab.com, Free tier | Personal access token for a service account | read_api, read_repository; user at least Reporter | CI job, schedule or manual rescan | CI job |
| One sensitive project only | Project access token | read_api, read_repository, Reporter | CI job | CI job |
| You want fix merge requests | Group or project access token | Add api, Developer; never Maintainer | Same | CI job, plus optional integration comments and commit status |
| Self-managed GitLab on a public address | Group or project access token from your instance | read_api, read_repository, Reporter | CI job, schedule or manual rescan | CI job |
| Self-managed GitLab on a private network only | None in Safeguard; the card refuses private addresses | Not applicable | CI job with --local on your runners | CI job with --local (SBOM and dependency vulnerabilities only) |
| You also want images from the GitLab registry | Same token | Add read_registry | Schedule (no registry push events) | Not applicable |
Rotation and disconnect
Rotate:
- Create a new token with the same role and scopes.
- Under Project Configurations on the Project/Product tab, open the connection's menu and choose Update.
- Paste the new token into Access Token, click Check, and click Save Changes.
- Revoke the old token in GitLab.
Disconnect:
- Choose Delete from the connection's menu under Project Configurations and confirm Delete configuration.
- Revoke the token under Settings › Access tokens for a group or project token, or Edit profile › Access › Personal access tokens for a personal token.
Rotate the CI secret key in Safeguard at the same time, so both credentials share one cadence.
Troubleshooting the common errors
These messages and fixes come straight from the guide and the CI pages.
| Message you see | Cause | Fix |
|---|---|---|
| Invalid or expired access token | GitLab refused the token, or it lacks read_api | Check the expiry and scopes, or create a new token with read_api and read_repository |
| This server address isn't allowed | The address is private, internal or contains a user name and password | Use the server's public https address |
| No projects in the list | A group or project token sees only its own group or project; a personal token sees only its user's projects | Use a group token on the right group, or add the user to the projects |
| git clone failed (on a scan) | The token lacks read_repository, expired, or lost its role | Add the scope or role, or paste a new token |
| The job is missing from the pipeline | The branch does not match the job's rules, or the pipeline has no test stage | Fix rules or set stage |
AUTHENTICATION_REQUIRED on a merge request | The variable is protected, so GitLab passes it only to protected branches | Turn Protect variable off for SAFEGUARD_API_KEY |
PROJECT_NOT_RESCANNABLE | The project has no repository Safeguard can clone | Connect the repository under Integrations, or use --local |
How GitLab requirements map to what Safeguard does
| Requirement or guidance | What it asks | What Safeguard's GitLab setup does | What you still own |
|---|---|---|---|
| SSDF PS.1.1 | Least privilege access to code | Reporter role and two read scopes for scanning; write only if you opt in | Choosing which groups to connect; reviewing token membership |
| SSDF PO.3.2 | Secure deployment and operation of tools | Documented scopes, roles and never needed permissions; token encrypted at rest | Token expiry, rotation and revocation in GitLab |
| SSDF RV.2.2 | Plan and implement risk responses | Gates with Block, Warn and Notify; exceptions with expiry; fix merge requests | Approving exceptions and merging fixes |
| OWASP CICD-SEC-1 | Flow control so changes get reviewed | Fix merge requests go through your MR approval rules; Maintainer never granted | Required approval rules and required pipelines in GitLab |
| OWASP CICD-SEC-2 and CICD-SEC-6 | Identity and credential hygiene | Group bot tokens, scoped CI secret key lockable to one project | Masking variables, rotation cadence, ownership of expiry emails |
| OWASP CICD-SEC-3 | Dependency chain abuse | SCA (GA) on every connected project and merge request | Acting on findings; choosing what the gate blocks |
What Safeguard does not do
- It does not connect GitLab servers on private addresses. The card refuses private and internal addresses by design. For an internal only GitLab, scan from your own runners with
safeguard ci scan --local, which produces the SBOM and dependency findings but not the SAST results the default mode adds. - It does not set up GitLab webhooks. New commits are not scanned automatically by the integration alone. You need a CI job, a schedule or a manual rescan, and container registry images are rescanned on a schedule.
- It does not offer GitLab OAuth sign in for the integration. The dialog takes an access token, and deploy tokens and CI job tokens do not work.
- It does not gate on SAST findings in CI yet.
--fail-oncovers dependency vulnerabilities only. SAST (GA) results show in Safeguard but do not fail the job. - It does not merge your fixes for you in the setup we recommend. Autonomous remediation ships off, every category Manual. Even when it is on, the human approval in this post comes from your settings and GitLab rules, which you own.
- It does not manage your GitLab tokens. Expiry, rotation and revocation happen in GitLab. Safeguard tells you when a token stops working; it does not create a new one.
- It does not replace GitLab's own access controls. Branch protection, required approvals and required pipelines are GitLab settings. Safeguard's gate is only a check if GitLab is configured to require it.
Common mistakes
Granting api on day one. It is tempting to give the token everything so you never have to come back. That turns a read only scanner into a tool with write access to every project the token reaches. Start with read_api and read_repository.
Using a personal access token from your own account. It sees every project you are a member of and stops working when you leave. Use a group access token, or a personal token on a dedicated service account.
Granting Maintainer "just in case". Maintainer is only for merging into protected branches. If the plan is that a human merges fixes, Maintainer works against the plan.
Protecting the SAFEGUARD_API_KEY variable. It looks safer, and it silently breaks the scan on every merge request from an unprotected branch. Mask it, leave Protect off, and keep the key's scopes and project lock tight instead.
Treating --fail-on critical as a full policy. It gates on dependency severity only. Use --fail-on-gate with policies when you need license rules, EPSS thresholds or exceptions.
Letting exceptions live forever. An exception without an expiry date becomes permanent risk acceptance. Set a date and let the gate ask again.
Forgetting the expiry emails. If nobody who owns the integration reads GitLab's expiry warnings, scans stop on the expiry date.
FAQ
Does Safeguard support self-managed GitLab?
Yes. On the Private tab, choose Self-managed under "Where does GitLab run?" and enter your server URL. The server must be reachable from the internet over a public address, because Safeguard does not send a token to private or internal addresses. For an internal only server, scan in GitLab CI with safeguard ci scan --local.
What GitLab token scopes does Safeguard need?
For scanning, read_api and read_repository. Add read_registry to pull container images from the GitLab registry, and api only for fix merge requests, merge request comments, the quality gate commit status and merging from Safeguard. write_repository and read_user are not needed.
What GitLab role should the Safeguard token have?
Reporter for scanning. Developer if you want fix merge requests and the commit status. Maintainer only if Safeguard should merge into a protected branch, which the setup in this post avoids. Safeguard never needs Owner.
Can I use a GitLab deploy token or CI job token?
No. GitLab does not accept deploy tokens on its REST API, so Safeguard cannot list projects with one, and a CI job token exists only inside a pipeline job. Use a group, project or personal access token.
Can I use group access tokens on GitLab Free?
Not on GitLab.com. GitLab's docs say group access tokens on GitLab.com need Premium or Ultimate, and Safeguard's guide says the same for project access tokens. On GitLab.com Free, use a personal access token on a dedicated service account.
Does Safeguard install webhooks in my GitLab projects?
No. The GitLab integration has no webhooks to set up. Projects are scanned on connect; after that, use a schedule under Integrations › Scheduled, a manual rescan, or a CI job.
How do I block a merge request on Safeguard findings?
Add the safeguard ci scan --wait job to .gitlab-ci.yml with --fail-on <severity> or --fail-on-gate <gate>, store the secret key as a masked, unprotected SAFEGUARD_API_KEY variable, and require a successful pipeline before merging in GitLab.
Will Safeguard merge fix merge requests on its own?
Not by default. Autonomous remediation ships switched off with every category set to Manual. When you turn it on, keep Require Approval on in Settings › AI Remediate, add a required approval rule in GitLab, and keep the token at Developer so Safeguard cannot merge into protected branches.
Does Safeguard store my GitLab source code?
No. The guide says the scanner makes a shallow clone into a temporary folder and deletes it when the scan ends. Safeguard stores the connection settings, the selected projects, the SBOM and findings, and the encrypted token.
Once GitLab is connected, the remaining work is fixing what the scans find. The section below covers Safeguard's general remediation options; the first, fix pull requests, is the same flow that opens fix merge requests on a GitLab project.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session. You install the CLI, run safeguard to open the interactive terminal and ask it to "fix the lodash CVE in this repo". It reads the finding and the advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test, prints a summary and pushes nothing. The same review first habit is what you want for fix merge requests in GitLab: the change is prepared for you, and a person decides whether it lands.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with its device code, and then scans from the terminal. It is a useful companion to the --local route for self-managed GitLab on a private network, where the scanning has to happen on infrastructure you control and only results leave it.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to Safeguard. You add Safeguard from Claude's connector directory, where it has a community listing, sign in with OAuth, and choose which tools Claude can use. Once your GitLab projects are scanning, you can ask Claude which of them carry a given component version or which findings are most urgent, within the tools your administrator has enabled.
Click through it at your own pace, or open it full screen.
To try this on your own GitLab, install the CLI and desktop app from https://safeguard.sh/download and follow the GitLab and CI/CD guides at https://docs.safeguard.sh/docs .
Quick check
What is the least privilege GitLab token for scanning with Safeguard?
A group access token with the Reporter role and the read_api and read_repository scopes. Add api and the Developer role only when you want fix merge requests, and never grant Maintainer if a human should merge.
Why does the Safeguard CI job fail with AUTHENTICATION_REQUIRED on merge requests?
The SAFEGUARD_API_KEY variable is protected, so GitLab passes it only to pipelines on protected branches. Keep it masked, turn Protect variable off, and limit the key to scans:trigger and scans:read on one project.
Sources
- Safeguard docs: GitLab integration guide
- Safeguard docs: ESSCM source code integrations, GitLab
- Safeguard docs: Source code integration FAQ
- Safeguard docs: GitLab CI/CD
- Safeguard docs: CI/CD Integration
- Safeguard docs: Policies and Gates
- Safeguard docs: Policy outcomes and actions
- Safeguard docs: AI Remediate
- GitLab docs: Personal access tokens
- GitLab docs: Group access tokens
- GitLab docs: Project access tokens
- GitLab docs: Roles and permissions
- GitLab docs: Deploy tokens
- GitLab docs: CI/CD variables
- GitLab docs: Merge request approvals
- OWASP Top 10 CI/CD Security Risks
- NIST SP 800-218, Secure Software Development Framework v1.1
Last reviewed: 25 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.