Skip to main content

GitLab Component

The Plumber GitLab Component lets you add compliance scanning directly to your GitLab CI/CD pipelines. It checks for security and compliance issues like:

  • Container images using mutable tags (latest, dev)
  • Container images from untrusted registries
  • Unprotected branches
  • Hardcoded jobs not from includes/components
  • Outdated includes/templates
  • Forbidden version patterns (e.g., main, HEAD)
  • Missing required components or templates
View on GitLab

Quick Start (GitLab.com)

Info

These instructions are for projects hosted on gitlab.com. For self-hosted GitLab instances, see Self-Hosted GitLab below.

  1. Create a GitLab token

    In GitLab, go to User Settings → Access Tokens (or create one here) 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.

    Using mr_comment or badge? The token needs the api scope (instead of read_api) to create/update merge request comments or project badges.

  2. Add the token to your project

    Go to your project’s Settings → CI/CD → Variables and add the token as GITLAB_TOKEN (masked recommended).

  3. Add to your pipeline

    Add this to your .gitlab-ci.yml:

    workflow:
    rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS # prevents duplicate pipelines
    when: never
    - if: $CI_COMMIT_BRANCH
    - if: $CI_COMMIT_TAG
    include:
    - component: gitlab.com/getplumber/plumber/plumber@v0.1.29
    # inputs:
    # stage: .pre | by default runs in .pre which only runs if there is at least another CI job in another stage

    Get 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 workflow:rules block ensures a single pipeline per push: MR pipeline when an MR exists, branch pipeline otherwise. This is the recommended GitLab pattern. If you already have workflow:rules in your .gitlab-ci.yml, keep yours and just add the include.

  4. Run your pipeline

    Plumber will now run on every pipeline (default branch, tags, and open merge requests) and report compliance issues.

Tip

Everything is customizable — GitLab URL, branch, threshold, and more. See Customize below.

Self-Hosted GitLab

If you’re running a self-hosted GitLab instance, you’ll need to host your own copy of the component since gitlab.com components can’t be accessed from your instance. There are two ways:

Compliance Controls

Plumber includes 12 compliance controls. Each can be enabled/disabled and customized in your .plumber.yaml:

ControlDescription
Container images must not use forbidden tagsFlags latest, dev, and other mutable tags. Can enforce digest pinning for all images
Container images must come from authorized sourcesEnsures images come from trusted registries
Branch must be protectedVerifies critical branches have proper protection
Pipeline must not include hardcoded jobsDetects jobs defined directly instead of from includes
Includes must be up to dateChecks if included templates have newer versions
Includes must not use forbidden versionsPrevents mutable version references like main, HEAD
Pipeline must include componentEnsures required CI/CD components are included; detects overridden jobs
Pipeline must include templateEnsures required templates are included; detects overridden jobs
Pipeline must not enable debug traceDetects CI_DEBUG_TRACE/CI_DEBUG_SERVICES leaking secrets in job logs
Pipeline must not use unsafe variable expansionDetects user-controlled variables in shell re-interpretation contexts (OWASP CICD-SEC-1)
Security jobs must not be weakenedDetects security scanning jobs neutralized by allow_failure, rules: overrides, or when: manual (OWASP CICD-SEC-4)
Pipeline must not execute unverified scriptsDetects curl | bash, wget | sh, and download-then-execute patterns without integrity verification (OWASP CICD-SEC-3)
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 (OWASP CICD-SEC-1). Normal usage like echo $CI_COMMIT_BRANCH is safe; only re-interpretation contexts like eval, sh -c, and bash -c are flagged.

Some legitimate usages (Helm deploys, Terraform, etc.) can be allowed via allowedPatterns:

pipelineMustNotUseUnsafeVariableExpansion:
enabled: true
dangerousVariables:
- CI_MERGE_REQUEST_TITLE
- CI_COMMIT_MESSAGE
- CI_COMMIT_REF_NAME
- CI_COMMIT_BRANCH
allowedPatterns:
- "helm.*--set.*\\$CI_"
- "terraform workspace select.*\\$CI_"
- "docker build.*--build-arg.*\\$CI_"

See the CLI documentation for the full list of flagged vs. safe patterns.

Security Job Weakening Detection

Detects security scanning jobs (SAST, Secret Detection, Container Scanning, Dependency Scanning, DAST, License Scanning) that have been silently neutralized through local overrides in .gitlab-ci.yml. Maps to OWASP CICD-SEC-4 (Poisoned Pipeline Execution).

Three independent sub-controls:

  • allowFailureMustBeFalse (default: off): Detects allow_failure: true. Opt-in because GitLab templates ship with this.
  • rulesMustNotBeRedefined (default: on): Detects rules: overrides with when: never or when: manual.
  • whenMustNotBeManual (default: on): Detects when: manual at job level.
securityJobsMustNotBeWeakened:
enabled: true
securityJobPatterns:
- "*-sast"
- "secret_detection"
- "container_scanning"
- "*_dependency_scanning"
- "dast"
- "dast_*"
- "license_scanning"
allowFailureMustBeFalse:
enabled: false
rulesMustNotBeRedefined:
enabled: true
whenMustNotBeManual:
enabled: true

See the CLI documentation for full details and examples.

Unverified Script Execution Detection

Detects CI/CD jobs that download and immediately execute scripts from the internet without integrity verification. Patterns like curl | bash, wget | sh, or download-then-execute sequences are flagged. Lines that include checksum verification (e.g., sha256sum, gpg --verify) between download and execution are automatically excluded.

Maps to OWASP CICD-SEC-3 (Dependency Chain Abuse) and CICD-SEC-8 (Ungoverned Usage of 3rd Party Services).

pipelineMustNotExecuteUnverifiedScripts:
enabled: true
trustedUrls: []
# - https://internal-artifacts.example.com/*

Add trusted URL patterns to trustedUrls (supports wildcards) to suppress findings for known-good sources. See the CLI documentation for full details.

Tip

See the full configuration reference for all control options.

Selective Control Execution

Run or skip specific controls using their names from the table above. Useful for targeted CI checks or skipping controls that aren’t relevant.

# Run only image-related controls
include:
- component: gitlab.com/getplumber/plumber/plumber@v0.1.29
inputs:
controls: containerImageMustNotUseForbiddenTags,containerImageMustComeFromAuthorizedSources
# Run everything except branch protection
include:
- component: gitlab.com/getplumber/plumber/plumber@v0.1.29
inputs:
skip_controls: branchMustBeProtected

Controls not selected are reported as skipped in the output. The controls and skip_controls inputs are mutually exclusive.

Customize

Override any input to fit your needs:

include:
- component: gitlab.com/getplumber/plumber/plumber@v0.1.29
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
# 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 GitLab report)
print_output: true # Print to stdout
# Job behavior
stage: test # Run in a different stage
allow_failure: true # Don't block pipeline on failure
gitlab_token: $MY_CUSTOM_TOKEN # Different variable name
verbose: true # Enable debug output

All Inputs

InputDefaultDescription
server_url$CI_SERVER_URLGitLab instance URL
project_path$CI_PROJECT_PATHProject to analyze
branch$CI_COMMIT_REF_NAMEBranch to analyze
gitlab_token$GITLAB_TOKENGitLab API token (requires read_api + read_repository scopes, or api scope if mr_comment or badge is enabled)
threshold100Minimum compliance % to pass
config_file(auto-detect)Path to config file (relative to repo root). Auto-detects .plumber.yaml in repo, falls back to default
output_fileplumber-report.jsonPath to write JSON results
pbom_fileplumber-pbom.jsonPath to write PBOM (Pipeline Bill of Materials) output
pbom_cyclonedx_fileplumber-cyclonedx-sbom.jsonPath to write CycloneDX SBOM (auto-uploaded as GitLab report)
print_outputtruePrint text output to stdout
stage.prePipeline 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 in your pipeline, set this to test or another stage
imagegetplumber/plumber:0.1Docker image to use
allow_failurefalseAllow job to fail without blocking
verbosefalseEnable debug output for troubleshooting
mr_commentfalsePost/update a compliance comment on the merge request (requires api scope)
badgefalseCreate/update a Plumber compliance badge on the project (requires api scope; only runs on default branch)
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_warningsfalseTreat configuration warnings (unknown keys) as errors (exit 2)

Configuration

Plumber works out of the box with sensible defaults embedded in the image.

The component automatically detects your configuration using this priority:

  1. config_file input set → Uses your specified path (relative to repo root)
  2. .plumber.yaml in repo root → Uses your repo’s config file
  3. No config found → Uses the default configuration embedded in the container

(Optional) Create a Configuration File

Option A: If you have the CLI installed (via Homebrew, Mise, or binary):

Terminal window
plumber config generate

This generates a default config file that you can customize.

Option B: Create manually based on the default config.

Example Output

Tip

The component automatically generates and uploads a JSON compliance report as a job artifact. It can also produce a PBOM (Pipeline Bill of Materials) and a CycloneDX SBOM — the CycloneDX file is automatically uploaded as a GitLab CycloneDX report for use with security tools. Configure via the pbom_file and pbom_cyclonedx_file inputs.

The output is color-coded in your CI/CD job logs for easy scanning — green for passing controls, red for failures.

Plumber CLI output showing compliance results

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.

include:
- component: gitlab.com/getplumber/plumber/plumber@v0.1.29
inputs:
mr_comment: true # Requires api scope on token

Merge request comment showing compliance results

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. This feature 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.

include:
- component: gitlab.com/getplumber/plumber/plumber@v0.1.29
inputs:
badge: true # Requires api scope on token

Project badge showing Plumber compliance percentage

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.

Troubleshooting

IssueSolution
GITLAB_TOKEN environment variable is requiredAdd GITLAB_TOKEN in Settings → CI/CD → Variables
401 UnauthorizedToken needs read_api + read_repository scopes, from a Maintainer or higher
403 Forbidden on MR settingsExpected on non-Premium GitLab; continues without that data
403 Forbidden on MR commentToken needs api scope (not read_api) when mr_comment: true
403 Forbidden on badgeToken needs api scope (not read_api) when badge: true
MR comment not postedmr_comment only works in merge request pipelines (CI_MERGE_REQUEST_IID must be set)
Badge not created/updatedToken needs api scope and Maintainer role (or higher) on the project
Component not foundFor self-hosted GitLab, you must import or mirror the component to your instance
Plumber job not runningThe 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 pushAdd workflow:rules to your .gitlab-ci.yml to prevent duplicate branch + MR pipelines (see Quick Start)
Plumber job skipped on branchThe component only runs on merge request events, the default branch, and tags. Open an MR or push to the default branch to trigger it

Info

Need help? Open an issue on GitHub or join our Discord.