GitLab
This page covers the GitLab CI/CD particulars of the Plumber CLI: how to authenticate, the controls that ship for GitLab, the issues they emit, a complete gitlab.controls: configuration block, and the merge-request / badge integration. For installation and the provider-agnostic command reference, see the main CLI page.
On GitLab, Plumber flags issues like:
- Container images using mutable tags (
latest,dev) or from untrusted registries - Unprotected branches, missing approval rules, non-compliant MR settings
- Hardcoded jobs / outdated includes / forbidden version patterns (
main,HEAD) - Missing required catalog components or shared templates
- Security jobs weakened via
allow_failure,rules:overrides,when: manual - Controlled CI/CD variables overridden in the pipeline YAML
- Unverified remote script execution patterns (
curl | bash,wget | sh) - Docker-in-Docker (
dind) services enabling container escape on shared runners
Quick Start
Install Plumber (Installation below): Homebrew, mise, a prebuilt binary, Docker, or from source.
Authenticate: create a
GITLAB_TOKENwithread_api+read_repositoryscopes (see Authentication below).Run the scan from inside your project, or against a remote one (see Running a scan):
Terminal window plumber analyzeRead the report: a per-control compliance breakdown and a letter grade, plus an optional JSON report, PBOM, and CycloneDX SBOM (see Example Output).
Installation
Info
Trying a beta? Homebrew, mise, and Docker Hub follow stable releases only. Beta binaries and checksums live on the GitHub Releases page; each beta’s release notes carry the verified download + checksum steps for that specific build.
brew tap getplumber/plumberbrew install plumberInstall a specific version:
brew install getplumber/plumber/plumber@v0.3.0Info
Versioned formulas are keg-only. Use the full path (e.g., /usr/local/opt/plumber@v0.3.0/bin/plumber) or run brew link plumber@v0.3.0 to add it to your PATH.
mise use -g github:getplumber/plumberInfo
Requires mise activation in your shell, or run with mise exec -- plumber.
Linux (amd64)
curl -LO https://github.com/getplumber/plumber/releases/latest/download/plumber-linux-amd64chmod +x plumber-linux-amd64sudo mv plumber-linux-amd64 /usr/local/bin/plumberLinux (arm64)
curl -LO https://github.com/getplumber/plumber/releases/latest/download/plumber-linux-arm64chmod +x plumber-linux-arm64sudo mv plumber-linux-arm64 /usr/local/bin/plumbermacOS (Apple Silicon)
curl -LO https://github.com/getplumber/plumber/releases/latest/download/plumber-darwin-arm64chmod +x plumber-darwin-arm64sudo mv plumber-darwin-arm64 /usr/local/bin/plumbermacOS (Intel)
curl -LO https://github.com/getplumber/plumber/releases/latest/download/plumber-darwin-amd64chmod +x plumber-darwin-amd64sudo mv plumber-darwin-amd64 /usr/local/bin/plumberWindows (PowerShell)
Invoke-WebRequest -Uri https://github.com/getplumber/plumber/releases/latest/download/plumber-windows-amd64.exe -OutFile plumber.exeVerify checksum (optional):
curl -LO https://github.com/getplumber/plumber/releases/latest/download/checksums.txtsha256sum -c checksums.txt --ignore-missingdocker pull getplumber/plumber:latestRun analysis directly with Docker:
docker run --rm \ -e GITLAB_TOKEN=glpat-xxxx \ getplumber/plumber:latest analyze \ --gitlab-url https://gitlab.com \ --project mygroup/myprojectgit clone https://github.com/getplumber/plumber.gitcd plumbermake build# or: make install (builds and copies to /usr/local/bin/)Info
Requires Go 1.24+ and Make.
Authentication
In GitLab, go to User Settings → Access Tokens (direct link) and create a Personal Access Token with read_api + read_repository scopes. Project Access Tokens also work: create one inside your project under Settings → Access Tokens with the same scopes and at least Maintainer role.
Caution
The token must belong to a user (or project bot) with Maintainer role (or higher) on the project to access branch protection settings and other project configurations.
export GITLAB_TOKEN=glpat-xxxxUse api scope instead of read_api if you plan to enable --mr-comment or --badge (see GitLab Integration below).
Running a scan
# Auto-detected from git remoteplumber analyze
# Explicit projectplumber analyze --gitlab-url https://gitlab.com --project mygroup/myprojectSelf-Hosted GitLab
plumber analyze --gitlab-url https://gitlab.example.com --project mygroup/myprojectCustom CI Configuration Path
By default Plumber reads the project’s configured CI config path (usually .gitlab-ci.yml). Override it when your pipeline file lives elsewhere:
plumber analyze --ci-config-path .gitlab/ci/main.ymlCommand Reference
plumber analyze
The main command for analyzing GitLab CI/CD pipelines.
plumber analyze [flags]Flags
| Flag | Required | Default | Description |
|---|---|---|---|
--gitlab-url | No* | auto-detect | GitLab instance URL (e.g., https://gitlab.com). Mutually exclusive with --github-url. |
--github-url | No* | auto-detect | GitHub host (e.g., github.com or ghes.example.com). Mutually exclusive with --gitlab-url. |
--project | No* | auto-detect | Project / repo path. GitLab: group/project. GitHub: owner/repo. |
--config | No | .plumber.yaml | Path to configuration file |
--threshold | No | 100 | Minimum compliance % to pass (0-100) |
--branch | No | Project default | Branch to analyze |
--output | No | - | Write JSON results to file |
--pbom | No | - | Write PBOM (Pipeline Bill of Materials) to file |
--pbom-cyclonedx | No | - | Write PBOM in CycloneDX SBOM format |
--print | No | true | Print text output to stdout |
--mr-comment | No | false | Post/update a compliance comment on the merge request (MR pipelines only; requires api scope) |
--badge | No | false | Create/update a Plumber compliance badge on the project (requires api scope; only runs on default branch) |
--controls | No | - | Run only listed controls (comma-separated). Cannot be used with --skip-controls |
--skip-controls | No | - | Skip listed controls (comma-separated). Cannot be used with --controls |
--fail-warnings | No | false | Treat configuration warnings (unknown keys) as errors (exit 2) |
--ci-config-path | No | auto-detect | Override CI configuration file path. Defaults to project CI config path from GitLab settings (usually .gitlab-ci.yml) |
--verbose, -v | No | false | Enable verbose/debug output |
Info
* Auto-detected from git remote (requires origin) if not specified. Supports both SSH and HTTPS remote URLs.
* You can always override with --gitlab-url and --project
Environment Variables
| Variable | Required | Description |
|---|---|---|
GITLAB_TOKEN | GitLab only | GitLab API token with read_api + read_repository scopes (from a Maintainer or higher). Use api scope instead if --mr-comment or --badge is enabled. |
PLUMBER_NO_UPDATE_CHECK | No | Set to any value (e.g., 1) to disable the automatic version check. |
For the configuration commands (plumber config init / generate / migrate / view / validate), plumber explain, and exit codes, see the main CLI page. They behave identically for both providers.
Usage Examples
Save JSON Output
docker run --rm \ -e GITLAB_TOKEN=glpat-xxxx \ -v $(pwd):/output \ getplumber/plumber:latest analyze \ --gitlab-url https://gitlab.com \ --project mygroup/myproject \ --branch main \ --config /.plumber.yaml \ --threshold 100 \ --output /output/results.jsonSilent Mode (JSON Only)
plumber analyze \ --gitlab-url https://gitlab.com \ --project mygroup/myproject \ --config .plumber.yaml \ --threshold 100 \ --output results.json \ --print falseExample Output
The CLI output is color-coded in your terminal for easy scanning: green for passing controls, red for failures.
Tip
When using --output, results are saved as JSON for programmatic access and CI/CD integration.

Available Controls
Plumber ships 14 GitLab controls. Each can be enabled / disabled and customized in .plumber.yaml. When a control detects a violation, it creates an Issue (e.g., ISSUE-101) with a direct link to its documentation page.
| Control | Issues | Description |
|---|---|---|
| Container images must come from authorized sources | ISSUE-101 | Ensures images come from trusted registries |
| Container images must not use forbidden tags | ISSUE-102 | Flags latest, dev, and other mutable tags. Can enforce digest pinning for all images |
| Container images must be pinned by digest | ISSUE-103 | Ensures images use SHA256 digest pinning |
| Pipeline must not enable debug trace | ISSUE-203 | Detects CI_DEBUG_TRACE/CI_DEBUG_SERVICES leaking secrets in job logs |
| Pipeline must not use unsafe variable expansion | ISSUE-204 | Detects user-controlled variables in shell re-interpretation contexts (OWASP CICD-SEC-1) |
| Pipeline must not override job variables | ISSUE-205 | Detects controlled CI/CD variables redefined in .gitlab-ci.yml that should only be set in GitLab CI/CD Settings |
| Pipeline must not include hardcoded jobs | ISSUE-401 | Detects jobs defined directly instead of from includes |
| Includes must be up to date | ISSUE-403 | Checks if included templates have newer versions |
| Includes must not use forbidden versions | ISSUE-404 | Prevents mutable version references like main, HEAD |
| Pipeline must include template | ISSUE-405 ISSUE-406 | Ensures required templates are included; detects overridden jobs |
| Pipeline must include component | ISSUE-408 ISSUE-409 | Ensures required CI/CD components are included; detects overridden jobs |
| Security jobs must not be weakened | ISSUE-410 | Detects security scanning jobs neutralized by allow_failure, rules: overrides, or when: manual (OWASP CICD-SEC-4) |
| Pipeline must not execute unverified scripts | ISSUE-411 | Detects curl | bash, wget | sh, and download-then-execute patterns without integrity verification (OWASP CICD-SEC-3) |
| Pipeline must not use Docker-in-Docker | ISSUE-412 ISSUE-413 | Detects docker:dind services and insecure Docker daemon configuration (for example TLS disabled) |
| Branch must be protected | ISSUE-501 ISSUE-505 | Verifies critical branches have proper protection |
Unsafe Variable Expansion Detection
Detects user-controlled CI variables (MR title, commit message, branch name) passed to commands that re-interpret their input as shell code. This is OWASP CICD-SEC-1.
GitLab sets CI variables as environment variables. The shell does not re-parse expanded values for command substitution, so normal usage is safe. Only commands that re-interpret their arguments as code are flagged:
Flagged (re-interpretation contexts):
eval "$CI_COMMIT_BRANCH"sh -c "$CI_MERGE_REQUEST_TITLE"bash -c "$CI_COMMIT_MESSAGE"source <(echo "$CI_COMMIT_REF_NAME")echo "$CI_COMMIT_BRANCH" | xargs shNot flagged (safe, the shell doesn’t re-parse env var values):
echo $CI_COMMIT_BRANCHcurl -d "$CI_MERGE_REQUEST_TITLE" https://...git checkout $CI_COMMIT_REF_NAMEprintf '%s' "$CI_COMMIT_MESSAGE"Allowing specific patterns
Some sh -c or bash -c usages are legitimate (e.g., Helm deploys, Terraform workspaces). Use allowedPatterns (regex) to suppress those findings. Each pattern is matched against the full script line.
pipelineMustNotUseUnsafeVariableExpansion: enabled: true dangerousVariables: - CI_MERGE_REQUEST_TITLE - CI_COMMIT_MESSAGE - CI_COMMIT_REF_NAME - CI_COMMIT_REF_SLUG - CI_COMMIT_BRANCH allowedPatterns: - "helm.*--set.*\\$CI_" - "terraform workspace select.*\\$CI_" - "docker build.*--build-arg.*\\$CI_" - "aws s3 sync.*\\$CI_" - "make deploy.*\\$CI_"For example, the script line sh -c "helm upgrade myapp . --set image.tag=$CI_COMMIT_SHA" would normally be flagged, but the pattern helm.*--set.*\\$CI_ allows it.
Info
Escape $ as \\$ and {/} as \\{/\\} in patterns. Only direct variable names are detected. Indirect aliasing (variables: { B: $CI_COMMIT_BRANCH } then sh -c $B) is not tracked.
Security Job Weakening Detection
GitLab lets you override any property of an included job. This means someone can include a security template (SAST, Secret Detection, Container Scanning, Dependency Scanning, DAST, License Scanning) but silently neutralize it. The pipeline still looks compliant, but the scanning is disabled. Maps to OWASP CICD-SEC-4 (Poisoned Pipeline Execution).
This control detects three weakening patterns, each a separate sub-control you can toggle independently:
allowFailureMustBeFalse(default: off, opt-in): Detectsallow_failure: true. Off by default because GitLab templates ship with this setting.rulesMustNotBeRedefined(default: on): Detectsrules:overrides withwhen: neverorwhen: manual.whenMustNotBeManual(default: on): Detectswhen: manualset at job level.
Flagged: security jobs weakened
include: - template: Security/SAST.gitlab-ci.yml
semgrep-sast: allow_failure: true # failures silently ignored
secret_detection: rules: - when: never # job will never run
container_scanning: when: manual # requires manual triggerPlumber identifies a job as a security job when its name (the job id as it appears in .gitlab-ci.yml, for example semgrep-sast) matches one of the globs in securityJobPatterns. Wildcards (*) match any substring.
gitlab: controls: securityJobsMustNotBeWeakened: enabled: true securityJobPatterns: - "*-sast" - "secret_detection" - "container_scanning" - "*_dependency_scanning" - "gemnasium-*" - "dast" - "dast_*" - "license_scanning" allowFailureMustBeFalse: enabled: false rulesMustNotBeRedefined: enabled: true whenMustNotBeManual: enabled: trueUnverified Script Execution Detection
Detects CI/CD jobs that download and immediately execute scripts from the internet without integrity verification. This is a well-documented supply chain attack vector: an attacker who compromises the remote URL can serve a modified script that exfiltrates secrets. Maps to OWASP CICD-SEC-3 (Dependency Chain Abuse) and CICD-SEC-8 (Ungoverned Usage of 3rd Party Services).
Detected patterns:
- Direct pipe to shell:
curl ... | bash,wget ... | sh,curl ... | python, etc. - Download-and-execute:
curl -o script.sh ... && bash script.sh - Download-redirect-execute:
curl ... > install.sh; sh install.sh
Lines that include checksum verification (e.g., sha256sum, gpg --verify) between the download and execution are automatically excluded.
pipelineMustNotExecuteUnverifiedScripts: enabled: true trustedUrls: [] # - https://internal-artifacts.example.com/*Add trusted URL patterns to trustedUrls (supports wildcards) to suppress findings for known-good sources.
Job Variable Override Detection
Detects CI/CD variables that are redefined in .gitlab-ci.yml when they should only be set in GitLab CI/CD Settings > Variables. An attacker who can modify .gitlab-ci.yml could override variables like SECURE_ANALYZERS_PREFIX to point to a fake registry, or set SAST_DISABLED: "true" to silently disable security scanners.
The control inspects only the raw user-authored YAML. Variables defined by included templates or components are not flagged.
pipelineMustNotOverrideJobVariables: enabled: true variables: - SECURE_ANALYZERS_PREFIX - SAST_DISABLED - SAST_EXCLUDED_PATHS - SAST_EXCLUDED_ANALYZERS - SECRET_DETECTION_DISABLED - SECRET_DETECTION_EXCLUDED_PATHS - CONTAINER_SCANNING_DISABLED - DAST_DISABLED - DEPENDENCY_SCANNING_DISABLED - LICENSE_SCANNING_DISABLEDAdd any variable name your organization considers controlled. Matching is case-insensitive and any value triggers the issue (even "false").
Docker-in-Docker Detection
Detects CI/CD jobs that use Docker-in-Docker (dind) services. Running a Docker daemon inside a CI container on shared runners in privileged mode enables container escape, lateral movement, and access to secrets from other jobs on the same runner.
When detectInsecureDaemon is enabled (default: true), the control also flags jobs where TLS is disabled (DOCKER_TLS_CERTDIR="") or the Docker host uses the plaintext port (tcp://docker:2375).
pipelineMustNotUseDockerInDocker: enabled: true detectInsecureDaemon: trueConsider using Kaniko or Buildah for building container images in CI/CD as safer alternatives.
Issues
The CLI reports Issues using identifiers like ISSUE-XXX. Each links to a documentation page with a description, the impact, and before/after fix examples. You can also inspect a code locally:
plumber explain ISSUE-412# shorthand:plumber explain 412Codes shipping today for GitLab:
| Issue | Title |
|---|---|
| ISSUE-101 | Untrusted image source |
| ISSUE-102 | Forbidden container image tag |
| ISSUE-103 | Container image not pinned by digest |
| ISSUE-203 | Pipeline enables CI debug trace |
| ISSUE-204 | Unsafe variable expansion |
| ISSUE-205 | Job variable overrides controlled variable |
| ISSUE-401 | Hardcoded job |
| ISSUE-403 | Outdated template |
| ISSUE-404 | Forbidden include version |
| ISSUE-405 | Missing required template |
| ISSUE-406 | Forbidden override of required template |
| ISSUE-408 | Missing required component |
| ISSUE-409 | Forbidden override of required component |
| ISSUE-410 | Security job weakened |
| ISSUE-411 | Unverified script execution |
| ISSUE-412 | Docker-in-Docker service detected |
| ISSUE-413 | Docker-in-Docker with insecure daemon configuration |
| ISSUE-501 | Branch protection missing |
| ISSUE-505 | Branch protection configuration not compliant |
Example Configuration
The gitlab.controls: section of a schema v2 .plumber.yaml:
version: "2.0"
gitlab: controls: containerImageMustNotUseForbiddenTags: enabled: true tags: - latest - dev - main # When true, ALL images must be pinned by digest. Takes precedence # over the tags list, so even version tags like alpine:3.19 fail. containerImagesMustBePinnedByDigest: false
containerImageMustComeFromAuthorizedSources: enabled: true trustDockerHubOfficialImages: true trustedUrls: - $CI_REGISTRY_IMAGE:* - registry.gitlab.com/security-products/*
branchMustBeProtected: enabled: true defaultMustBeProtected: true namePatterns: - main - release/* allowForcePush: false minMergeAccessLevel: 30 # Developer minPushAccessLevel: 40 # Maintainer
pipelineMustNotIncludeHardcodedJobs: enabled: true
includesMustBeUpToDate: enabled: true
includesMustNotUseForbiddenVersions: enabled: true forbiddenVersions: - latest - "~latest" - main - master - HEAD defaultBranchIsForbiddenVersion: false
pipelineMustIncludeComponent: enabled: false # Disabled by default. Enable and configure for your org. # Expression syntax (use one, not both): # required: components/sast/sast AND components/secret-detection/secret-detection # Array syntax (OR of ANDs): # requiredGroups: # - ["components/sast/sast", "components/secret-detection/secret-detection"] # - ["your-org/full-security/full-security"]
pipelineMustIncludeTemplate: enabled: false # Disabled by default. Enable and configure for your org. # Expression syntax (use one, not both): # required: templates/go/go AND templates/trivy/trivy # Array syntax (OR of ANDs): # requiredGroups: # - ["templates/go/go", "templates/trivy/trivy"] # - ["templates/full-go-pipeline"]
# Detect debug trace variables that leak secrets in job logs. pipelineMustNotEnableDebugTrace: enabled: true forbiddenVariables: - CI_DEBUG_TRACE - CI_DEBUG_SERVICES
# Detect user-controlled variables in shell re-interpretation contexts # (eval, sh -c, etc.). Safe: echo $CI_COMMIT_BRANCH. Dangerous: eval # "deploy $CI_COMMIT_BRANCH". pipelineMustNotUseUnsafeVariableExpansion: enabled: true dangerousVariables: - CI_MERGE_REQUEST_TITLE - CI_MERGE_REQUEST_DESCRIPTION - CI_COMMIT_MESSAGE - CI_COMMIT_TITLE - CI_COMMIT_TAG_MESSAGE - CI_COMMIT_REF_NAME - CI_COMMIT_REF_SLUG - CI_COMMIT_BRANCH - CI_MERGE_REQUEST_SOURCE_BRANCH_NAME - CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME # Regex patterns to allow specific script lines (escape $ as \\$). allowedPatterns: - "helm.*--set.*\\$CI_" - "terraform workspace select.*\\$CI_" - "docker build.*--build-arg.*\\$CI_"
# Detect security scanning jobs that have been silently weakened. securityJobsMustNotBeWeakened: enabled: true securityJobPatterns: - "*-sast" - "secret_detection" - "container_scanning" - "*_dependency_scanning" - "gemnasium-*" - "dast" - "dast_*" - "license_scanning" allowFailureMustBeFalse: enabled: false # opt-in: GitLab templates ship with allow_failure: true rulesMustNotBeRedefined: enabled: true whenMustNotBeManual: enabled: true
# Detect controlled variables overridden in .gitlab-ci.yml. pipelineMustNotOverrideJobVariables: enabled: true variables: - SECURE_ANALYZERS_PREFIX - SAST_DISABLED - SAST_EXCLUDED_PATHS - SECRET_DETECTION_DISABLED - CONTAINER_SCANNING_DISABLED - DAST_DISABLED
# Detect unverified script downloads and execution (curl|bash, wget|sh). pipelineMustNotExecuteUnverifiedScripts: enabled: true trustedUrls: [] # - https://internal-artifacts.example.com/*
# Detect Docker-in-Docker services and insecure daemon configuration. pipelineMustNotUseDockerInDocker: enabled: true detectInsecureDaemon: trueInfo
pipelineMustIncludeComponent and pipelineMustIncludeTemplate support two syntax options for defining requirements (use one, not both):
- Expression syntax (
required): A natural boolean expression usingAND,OR, and parentheses.ANDbinds tighter thanOR. - Array syntax (
requiredGroups): A list of groups using “OR of ANDs” logic. Outer array = OR, inner array = AND.
GitLab Integration
Plumber integrates directly with GitLab to provide visual compliance feedback where your team works.
Merge Request Comments
Automatically post compliance summaries on merge requests to catch issues before they’re merged.
plumber analyze --mr-commentOr via the GitLab CI component:
include: - component: gitlab.com/getplumber/plumber/plumber@v0.2.2 inputs: mr_comment: true # Requires api scope on token
Features:
- Shows compliance badge with pass/fail status
- Lists all controls with individual compliance percentages
- Details specific issues found with job names and image references
- Automatically updates on each pipeline run (no duplicate comments)
Caution
Token requirement: The api scope is required (not read_api) to create/update MR comments. The --mr-comment flag only works in merge request pipelines (CI_MERGE_REQUEST_IID must be set).
Project Badges
Display a live compliance badge on your project’s overview page.
plumber analyze --badgeOr via the GitLab CI component:
include: - component: gitlab.com/getplumber/plumber/plumber@v0.2.2 inputs: badge: true # Requires api scope on token
Features:
- Shows current compliance percentage
- Green when compliance meets threshold, red when below
- Only updates on default branch pipelines (not on MRs or feature branches)
- Badge appears in GitLab’s “Project information” section
Caution
Token requirement: The api scope is required (not read_api) and Maintainer role to manage project badges.
The Plumber GitLab CI/CD component (drop-in pipeline integration) is documented in the GitLab CI Component section below. For installation and the provider-agnostic commands, see the main CLI page.
Selective Control Execution
You can run or skip specific controls using their YAML key names from .plumber.yaml. This is useful for iterative debugging or targeted CI checks.
# Only check image tags and branch protectionplumber analyze --controls containerImageMustNotUseForbiddenTags,branchMustBeProtected
# Run everything except branch protectionplumber analyze --skip-controls branchMustBeProtectedControls not selected are reported as skipped in the output. The --controls and --skip-controls flags are mutually exclusive.
Valid GitLab control names
| Control Name |
|---|
branchMustBeProtected |
containerImageMustComeFromAuthorizedSources |
containerImageMustNotUseForbiddenTags |
includesMustBeUpToDate |
includesMustNotUseForbiddenVersions |
pipelineMustIncludeComponent |
pipelineMustIncludeTemplate |
pipelineMustNotEnableDebugTrace |
pipelineMustNotExecuteUnverifiedScripts |
pipelineMustNotIncludeHardcodedJobs |
pipelineMustNotOverrideJobVariables |
pipelineMustNotUseDockerInDocker |
pipelineMustNotUseUnsafeVariableExpansion |
securityJobsMustNotBeWeakened |
GitLab CI Component
Beyond running the CLI yourself, Plumber ships as a GitLab CI/CD component: a drop-in include: that runs compliance scanning on every pipeline with no binary to install. It uses the same engine, the same .plumber.yaml, and the same controls and issues documented above. It produces the same output and supports the same MR comments and badges.
Quick Start (GitLab.com)
Info
For self-hosted GitLab instances, see Hosting on self-hosted GitLab below.
Create a GitLab token
Use a token with
read_api+read_repositoryscopes (orapiif you enablemr_comment/badge), as described under Authentication above.Add the token to your project
Go to Settings → CI/CD → Variables and add it as
GITLAB_TOKEN(masked recommended).Add the component to your
.gitlab-ci.ymlworkflow:rules:- if: $CI_PIPELINE_SOURCE == "merge_request_event"- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS # prevents duplicate pipelineswhen: never- if: $CI_COMMIT_BRANCH- if: $CI_COMMIT_TAGinclude:- component: gitlab.com/getplumber/plumber/plumber@v0.2.2# inputs:# stage: .pre | by default runs in .pre which only runs if there is at least another CI job in another stageGet the latest version from the CI/CD Catalog.
Info
Why
workflow:rules? Without it, pushing to a branch with an open merge request creates two pipelines (a branch pipeline and an MR pipeline), splitting your jobs between them. The block above ensures a single pipeline per push: MR pipeline when an MR exists, branch pipeline otherwise. This is the recommended GitLab pattern. If you already haveworkflow:rules, keep yours and just add theinclude.Run your pipeline
Plumber now runs on every pipeline (default branch, tags, and open merge requests) and reports compliance issues.
Hosting on self-hosted GitLab
If you run a self-hosted GitLab instance, you need your own copy of the component since gitlab.com components can’t be accessed from your instance. There are two ways:
Import the upstream repository directly into your GitLab instance.
Import the repository
Go to New Project → Import project → Repository by URL and use
https://gitlab.com/getplumber/plumber.git. Choose a group and project name (e.g.,infrastructure/plumber).Enable the CI/CD Catalog
In the imported project, go to Settings → General, ensure the project has a description (required for the Catalog), expand Visibility, project features, permissions, toggle CI/CD Catalog resource on, and save.
Publish a release
The imported project comes with upstream tags. Run a pipeline on an existing tag to trigger the release: CI/CD → Pipelines → Run pipeline, select an imported tag (e.g.,
v0.2.1), and click Run pipeline. This creates a Catalog release for that tag. (Alternatively, create a tag manually under Code → Tags → New tag, though that can conflict when fetching remote tags later.)Add the token
Create a token as described under Authentication, then add it under the scanned project’s Settings → CI/CD → Variables as
GITLAB_TOKEN(masked recommended).Use the component
include:- component: gitlab.example.com/infrastructure/plumber/plumber@v0.2.2To update: re-import or manually pull upstream changes.
Fork on gitlab.com, then set up a pull mirror on your self-hosted instance so it stays in sync automatically.
Fork on gitlab.com
Fork getplumber/plumber under your gitlab.com namespace (e.g.,
your-org/plumber).Create a mirrored project on your instance
New Project → Import project → Repository by URL with
https://gitlab.com/your-org/plumber.git.Set up pull mirroring
In the project, Settings → Repository → Mirroring repositories: add
https://gitlab.com/your-org/plumber.git, direction Pull, plus a gitlab.com token withread_repositoryscope if the fork is private.Info
Pull mirroring syncs automatically (every 30 minutes on Premium, manual on other tiers). On a new upstream release, sync your gitlab.com fork first, then the mirror picks it up.
Enable the CI/CD Catalog
Settings → General: ensure a project description, then toggle CI/CD Catalog resource on and save.
Publish a release
Run a pipeline on an existing imported tag (CI/CD → Pipelines → Run pipeline) to create a Catalog release.
Add the token
As under Authentication, then add it as
GITLAB_TOKENunder the scanned project’s Settings → CI/CD → Variables.Use the component
include:- component: gitlab.example.com/infrastructure/plumber/plumber@v0.2.2
Customizing the component
Override any input to fit your needs:
include: - component: gitlab.com/getplumber/plumber/plumber@v0.2.2 inputs: # Target (defaults to current project) server_url: https://gitlab.example.com # Self-hosted GitLab project_path: other-group/other-project # Analyze a different project branch: develop # Analyze a specific branch ci_config_path: $CI_CONFIG_PATH # CI config path (GitLab predefined variable)
# Compliance threshold: 80 # Minimum % to pass (default: 100) config_file: configs/my-plumber.yaml # Custom config path
# Output output_file: plumber-report.json # Export JSON report pbom_file: plumber-pbom.json # PBOM artifact pbom_cyclonedx_file: plumber-cyclonedx-sbom.json # CycloneDX SBOM (auto-uploaded as a GitLab report) print_output: true
# Job behavior stage: test # Run in a different stage allow_failure: true # Don't block the pipeline on failure gitlab_token: $MY_CUSTOM_TOKEN # Different variable name verbose: true
# Selective execution (mutually exclusive) controls: containerImageMustNotUseForbiddenTags,branchMustBeProtected # skip_controls: branchMustBeProtected
# MR feedback (require `api` scope, see GitLab Integration above) mr_comment: true badge: trueThe controls / skip_controls inputs map to the CLI --controls / --skip-controls flags (valid names). mr_comment and badge are the component equivalents of the CLI --mr-comment / --badge flags shown under GitLab Integration.
All inputs
| Input | Default | Description |
|---|---|---|
server_url | $CI_SERVER_URL | GitLab instance URL |
project_path | $CI_PROJECT_PATH | Project to analyze |
branch | $CI_COMMIT_REF_NAME | Branch to analyze |
ci_config_path | $CI_CONFIG_PATH | CI configuration file path to analyze. Defaults to the GitLab predefined variable (.gitlab-ci.yml unless customized in project settings) |
gitlab_token | $GITLAB_TOKEN | GitLab API token (read_api + read_repository, or api if mr_comment / badge is enabled) |
threshold | 100 | Minimum compliance % to pass |
config_file | (auto-detect) | Path to config file (relative to repo root). Auto-detects .plumber.yaml, falls back to default |
output_file | plumber-report.json | Path to write JSON results |
pbom_file | plumber-pbom.json | Path to write the PBOM |
pbom_cyclonedx_file | plumber-cyclonedx-sbom.json | Path to write the CycloneDX SBOM (auto-uploaded as a GitLab report) |
print_output | true | Print text output to stdout |
stage | .pre | Pipeline stage for the job. .pre runs before all other stages but requires at least one job in a regular stage. If Plumber is the only job, set this to test or another stage |
image | getplumber/plumber:0.1 | Docker image to use |
allow_failure | false | Allow the job to fail without blocking |
verbose | false | Enable debug output |
mr_comment | false | Post/update a compliance comment on the merge request (requires api scope) |
badge | false | Create/update a compliance badge (requires api scope; default branch only) |
controls | - | Run only listed controls (comma-separated). Cannot be used with skip_controls |
skip_controls | - | Skip listed controls (comma-separated). Cannot be used with controls |
fail_warnings | false | Treat configuration warnings (unknown keys) as errors (exit 2) |
Component configuration resolution
The component resolves your configuration in priority order:
config_fileinput set uses your specified path (relative to repo root)..plumber.yamlin repo root uses your repo’s config file.- No config found uses the default embedded in the container.
To author one, run plumber config generate (see the main CLI page) or create it manually from the default config. The CycloneDX SBOM the component writes is automatically uploaded as a GitLab CycloneDX report.
Troubleshooting
| Issue | Solution |
|---|---|
GITLAB_TOKEN environment variable is required | Set the GITLAB_TOKEN environment variable with a valid GitLab token |
401 Unauthorized | Token needs read_api + read_repository scopes, from a Maintainer or higher |
403 Forbidden on MR settings | Expected on non-Premium GitLab; continues without that data |
403 Forbidden on MR comment | Token needs api scope (not read_api) when --mr-comment is enabled |
403 Forbidden on badge | Token needs api scope (not read_api) when --badge is enabled |
404 Not Found | Verify the project path and GitLab URL are correct |
| MR comment not posted | --mr-comment only works in merge request pipelines (CI_MERGE_REQUEST_IID must be set) |
| Badge not created/updated | Token needs api scope and Maintainer role (or higher) on the project |
| Configuration file not found | Ensure --config points at the real file (use an absolute path in Docker). Create one with plumber config generate or plumber config init |
| Component not found (self-hosted) | You must import or mirror the component to your instance (Hosting on self-hosted GitLab) |
| Plumber component job not running | The component’s default stage is .pre, which requires at least one other job in a regular stage. Override with inputs: { stage: test } |
| Two pipelines on the same push | Add workflow:rules to prevent duplicate branch + MR pipelines (see Quick Start) |
| Component job skipped on branch | The component runs only on merge request events, the default branch, and tags |