Skip to content

Open source CLI to check compliance of your GitLab CI/CD pipelines and repos

License

Notifications You must be signed in to change notification settings

getplumber/plumber

Repository files navigation

Plumber

CI/CD compliance scanner for GitLab pipelines

Build Status Latest Release Go Version GitHub Downloads Docker Pulls License

WebsiteDiscordIssues


🤔 What is Plumber?

Plumber is a compliance scanner for GitLab. It reads your .gitlab-ci.yml and repository settings, then 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 external includes/components
  • Outdated includes/templates
  • Forbidden version patterns (e.g., main, HEAD)
  • Missing required components or templates
  • Debug trace variables (CI_DEBUG_TRACE) leaking secrets in job logs
  • Unsafe variable injection via eval/sh -c/bash -c (OWASP CICD-SEC-1)

How does it work? Plumber connects to your GitLab instance via API, analyzes your pipeline configuration, and reports any issues it finds. You define what's allowed in a config file (.plumber.yaml), and Plumber tells you if your project complies. When running locally from your git repo, Plumber uses your local .gitlab-ci.yml allowing you to validate changes before pushing.

Plumber Demo

🚀 Two Ways to Use Plumber

Choose one of these methods. You don't need both:

Method Best for How it works
CLI Quick evaluation, local testing, one-off scans Install binary and run from terminal
GitLab CI Component Automated checks on every pipeline run Add 2 lines to your .gitlab-ci.yml

📖 Table of Contents


Option 1: CLI

Try Plumber in 2 minutes! No commits, no CI changes, just run it.

Step 1: Install

Choose one of the following:

Homebrew

brew tap getplumber/plumber
brew install plumber

Mise

mise use -g github:getplumber/plumber

Requires mise activation in your shell, or run with mise exec -- plumber.

Direct Download

# For Linux/MacOs
curl -LO "https://github.com/getplumber/plumber/releases/latest/download/plumber-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')"
chmod +x plumber-* && sudo mv plumber-* /usr/local/bin/plumber

📦 See Installation for Windows, Docker, or building from source.

Step 2: Generate a Config File

plumber config generate

This creates .plumber.yaml with default compliance rules. You can customize it later.

Step 3: Create & Set Your Token

  1. In GitLab, go to User Settings → Access Tokens (direct link)
  2. Create a Personal Access Token with read_api + read_repository scopes
    • Project Access Tokens also work: create one inside your project: Settings → Access Tokens with the same scopes and at least Maintainer role
  3. Export it in your terminal:

⚠️ Important: 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-xxxx

Step 4: Run Analysis

Plumber auto-detects the GitLab URL and project from your git remote but requires the remote to be set to 'origin'.

# if in git remote with remote = origin, run:
plumber analyze

# Or specify the project explicitly:
plumber analyze --gitlab-url https://gitlab.com --project mygroup/myproject

It reads your .plumber.yaml config and outputs a compliance report. You can also tell it to store the output in JSON format with the --output flag.

Local CI Configuration

When running from your project's git repository, Plumber automatically uses your local .gitlab-ci.yml instead of fetching it from the remote. This lets you validate changes before pushing.

The source of the .gitlab-ci.yml is resolved by priority:

  1. --branch is specified → always uses the remote file from that branch
  2. In a git repo and the local repo matches the analyzed project → uses the local file
  3. Otherwise → uses the remote file from the project's default branch

If the local CI configuration is invalid, Plumber exits with an error showing the specific validation messages from GitLab so you can fix issues before pushing.

Note: When using local CI configuration, include:local files are also read from your local filesystem. Other include types (components, templates, project files, remote URLs) are always resolved from their remote sources. Jobs from include:local files are treated as hardcoded by the analysis since they are project-specific and not from reusable external sources.

💡 Like what you see? Add Plumber to your CI/CD with the GitLab CI Component for automated checks on every pipeline.


Option 2: GitLab CI Component

Add Plumber to your GitLab pipeline: it will run automatically on the default branch, tags and open merge requests.

💬 These instructions are for gitlab.com. Self-hosted? See Self-Hosted GitLab.

Step 1: Create a GitLab Token

  1. In GitLab, go to User Settings → Access Tokens (or create one here)
  2. Create a Personal Access Token with read_api + read_repository scopes
    • Project Access Tokens also work: create one inside your project: Settings → Access Tokens with the same scopes and at least Maintainer role
  3. Go to your project's Settings → CI/CD → Variables
  4. Add the token as GITLAB_TOKEN (masked recommended)

⚠️ Important: 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.

Step 2: 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.28
    # 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 Catalog

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.

Step 3: Run Your Pipeline

That's it! Plumber will now run on every pipeline and report compliance issues.

💡 Want to customize? See Configuration to set thresholds, enable/disable controls, and whitelist trusted images.


⚙️ Configuration

GitLab CI Component Inputs

Override any input to fit your needs:

include:
  - component: gitlab.com/getplumber/plumber/plumber@v0.1.28
    inputs:
      threshold: 80                           # Minimum % to pass (default: 100)
      config_file: configs/my-plumber.yaml    # Custom config path
      verbose: true                           # Debug output

📦 Find the latest version on the GitLab CI/CD Catalog

All available 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
gitlab_token $GITLAB_TOKEN GitLab API token (requires read_api + read_repository)
threshold 100 Minimum compliance % to pass
config_file (auto-detect) Path to config file (relative to repo root)
output_file plumber-report.json Path to write JSON results
pbom_file plumber-pbom.json Path to write PBOM output
pbom_cyclonedx_file plumber-cyclonedx-sbom.json Path to write CycloneDX SBOM (auto-uploaded as 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 in your pipeline, set this to test or another stage
image getplumber/plumber:0.1 Docker image to use
allow_failure false Allow 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 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_warnings false Treat configuration warnings (unknown keys) as errors (exit 1)

Configuration File

Generate a default configuration file with:

plumber config generate

Flags:
  -f, --force           Overwrite existing file
  -o, --output string   Output file path (default ".plumber.yaml")

This creates .plumber.yaml with sensible defaults. Customize it to fit your needs.

Available Controls

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

1. Container images must not use forbidden tags

Detects container images using mutable tags that are expected to change unexpectedly.

When containerImagesMustBePinnedByDigest is set to true, this control operates in strict mode: all images must be pinned by digest (e.g., alpine@sha256:...). This takes precedence over the forbidden tags list even standard version tags like alpine:3.19 or node:20 will be flagged.

containerImageMustNotUseForbiddenTags:
  enabled: true
  tags:
    - latest
    - dev
    - development
    - staging
    - main
    - master
  # When true, ALL images must be pinned by digest (takes precedence over tags list)
  containerImagesMustBePinnedByDigest: false
2. Container images must come from authorized sources

Ensures container images come from trusted registries only.

containerImageMustComeFromAuthorizedSources:
  enabled: true
  trustDockerHubOfficialImages: true
  trustedUrls:
    - docker.io/docker:*
    - gcr.io/kaniko-project/*
    - $CI_REGISTRY_IMAGE:*
    - $CI_REGISTRY_IMAGE/*
    - getplumber/plumber:*
    - docker.io/getplumber/plumber:*
    - registry.gitlab.com/security-products/*
3. Branch must be protected

Verifies that critical branches have proper protection settings.

branchMustBeProtected:
  enabled: true
  defaultMustBeProtected: true
  namePatterns:
    - main
    - master
    - release/*
    - production
    - dev
  allowForcePush: false
  codeOwnerApprovalRequired: false
  minMergeAccessLevel: 30   # Developer
  minPushAccessLevel: 40    # Maintainer
4. Pipeline must not include hardcoded jobs

Detects jobs that are project-specific rather than coming from reusable external sources (components, templates, project file includes from other repos, remote URLs). This includes jobs defined directly in .gitlab-ci.yml as well as jobs from include:local files since local includes are just the project's CI config split across files, not reusable external sources.

pipelineMustNotIncludeHardcodedJobs:
  enabled: true
5. Includes must be up to date

Checks if included templates/components have newer versions available.

includesMustBeUpToDate:
  enabled: true
6. Includes must not use forbidden versions

Prevents use of mutable version references for includes that can change unexpectedly.

includesMustNotUseForbiddenVersions:
  enabled: true
  forbiddenVersions:
    - latest
    - "~latest"
    - main
    - master
    - HEAD
  defaultBranchIsForbiddenVersion: false
7. Pipeline must include component

Ensures required GitLab CI/CD components are included in the pipeline. Components that are imported but have their jobs overridden with forbidden CI/CD keywords (e.g., script, image, rules) are flagged as overridden. They still count as imported but produce separate issues and reduce compliance to 50% for that component.

There are two ways to define requirements (use one, not both):

Expression syntax: a natural boolean expression using AND, OR, and parentheses:

pipelineMustIncludeComponent:
  enabled: true
  # AND binds tighter than OR, so "a AND b OR c" means "(a AND b) OR c"
  required: components/sast/sast AND components/secret-detection/secret-detection

  # With alternatives:
  # required: (components/sast/sast AND components/secret-detection/secret-detection) OR your-org/full-security/full-security

Array syntax: a list of groups using "OR of ANDs" logic:

pipelineMustIncludeComponent:
  enabled: true
  # Outer array = OR (at least one group must be satisfied)
  # Inner array = AND (all components in group must be present)
  requiredGroups:
    - ["components/sast/sast", "components/secret-detection/secret-detection"]
    - ["your-org/full-security/full-security"]
8. Pipeline must include template

Ensures required templates (project includes) are present in the pipeline. Templates that are imported but have their jobs overridden with forbidden CI/CD keywords (e.g., script, image, rules) are flagged as overridden; they still count as imported but produce separate issues and reduce compliance to 50% for that template.

There are two ways to define requirements (use one, not both):

Expression syntax: a natural boolean expression using AND, OR, and parentheses:

pipelineMustIncludeTemplate:
  enabled: true
  required: templates/go/go AND templates/trivy/trivy AND templates/iso27001/iso27001

  # With alternatives:
  # required: (templates/go/go AND templates/trivy/trivy) OR templates/full-go-pipeline

Array syntax: a list of groups using "OR of ANDs" logic:

pipelineMustIncludeTemplate:
  enabled: true
  requiredGroups:
    - ["templates/go/go", "templates/trivy/trivy", "templates/iso27001/iso27001"]
    - ["templates/full-go-pipeline"]
9. Pipeline must not enable debug trace

Detects CI/CD pipelines that set CI_DEBUG_TRACE or CI_DEBUG_SERVICES to "true" in global or job-level variables. When enabled, GitLab prints ALL environment variables in job logs, including masked secrets like CI_JOB_TOKEN.

pipelineMustNotEnableDebugTrace:
  enabled: true
  forbiddenVariables:
    - CI_DEBUG_TRACE
    - CI_DEBUG_SERVICES
10. Pipeline must not use unsafe variable expansion

Detects user-controlled CI variables (MR title, commit message, branch name) passed to commands that re-interpret their input as shell code. An attacker can craft a branch name or MR title to inject arbitrary commands: 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 / dash -c / zsh -c / ksh -c
  • source <(echo "$CI_COMMIT_REF_NAME")
  • envsubst '$CI_COMMIT_MESSAGE' < tpl.sh | sh
  • echo "$CI_COMMIT_BRANCH" | xargs sh

Not flagged: safe, shell doesn't re-parse env var values:

  • echo $CI_COMMIT_BRANCH / echo "$CI_COMMIT_MESSAGE"
  • curl -d "$CI_MERGE_REQUEST_TITLE" https://...
  • git checkout $CI_COMMIT_REF_NAME
  • printf '%s' "$CI_COMMIT_MESSAGE"

Limitation: only direct variable names are detected. Indirect aliasing (variables: { B: $CI_COMMIT_BRANCH } then sh -c $B) is not tracked.

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
  allowedPatterns: []

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.

Run only specific controls:

# Only check image tags and branch protection
plumber analyze --controls containerImageMustNotUseForbiddenTags,branchMustBeProtected

Skip specific controls:

# Run everything except branch protection (avoids API calls you don't need)
plumber analyze --skip-controls branchMustBeProtected

In the GitLab CI component:

include:
  - component: gitlab.com/getplumber/plumber/plumber@v0.1.28
    inputs:
      controls: containerImageMustNotUseForbiddenTags,containerImageMustComeFromAuthorizedSources

Controls not selected are reported as skipped in the output. The --controls and --skip-controls flags are mutually exclusive.

Valid control names
Control Name
branchMustBeProtected
containerImageMustComeFromAuthorizedSources
containerImageMustNotUseForbiddenTags
includesMustBeUpToDate
includesMustNotUseForbiddenVersions
pipelineMustIncludeComponent
pipelineMustIncludeTemplate
pipelineMustNotEnableDebugTrace
pipelineMustNotIncludeHardcodedJobs
pipelineMustNotUseUnsafeVariableExpansion

📊 Artifacts & Outputs

Plumber generates multiple output formats to fit different workflows. All artifacts are available via CLI flags and are automatically configured when using the GitLab CI component.

Format CLI Flag CLI Default Component Default Description
Terminal --print true true Colorized compliance report
JSON Report --output plumber-report.json Machine-readable analysis results
PBOM --pbom plumber-pbom.json Pipeline Bill of Materials
CycloneDX --pbom-cyclonedx plumber-cyclonedx-sbom.json Standard SBOM format

JSON Report

Export the full analysis results in JSON format for CI integration, dashboards, or further processing:

plumber analyze --output plumber-report.json

The JSON includes all control results, compliance scores, issues found, and project metadata.

Pipeline Bill of Materials (PBOM)

Generate a complete inventory of all dependencies in your CI/CD pipeline:

plumber analyze --pbom pbom.json

The PBOM includes:

  • Container images with registry, tag, and digest information
  • CI/CD components with version and source
  • Templates and includes with version tracking
  • Compliance status for each dependency
  • Override detection - includes whose jobs are overridden with forbidden CI/CD keywords

CycloneDX SBOM

Generate a standards-compliant SBOM for security tool integration:

plumber analyze --pbom-cyclonedx pipeline-sbom.json

The CycloneDX output follows the CycloneDX 1.5 specification and is compatible with:

  • Grype and Trivy for vulnerability scanning
  • Dependency-Track for continuous monitoring
  • GitLab Dependency Scanning (auto-uploaded when using the component)

Note: CI/CD components and templates do not have CVEs in public vulnerability databases. The PBOM is primarily an inventory and compliance tool. For image vulnerability scanning, use dedicated tools like trivy image or grype.

📖 See docs/PBOM.md for full format documentation and field reference.

Terminal Output

Plumber provides colorized terminal output for easy scanning:

Plumber Output Example

  • Green checkmarks (✓) indicate passing controls
  • Red crosses (✗) indicate failing controls
  • Yellow bullets (•) highlight specific issues found
  • Summary tables show compliance percentages at a glance

🔗 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.28
    inputs:
      mr_comment: true  # Requires api scope on token

Merge Request Comment

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 (doesn't create duplicate comments)

⚠️ Token requirement: The api scope is required (not read_api) to create/update MR comments.

Project Badges

Display a live compliance badge on your project's overview page.

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

Project Badge

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

⚠️ Token requirement: The api scope is required (not read_api) and Maintainer role to manage project badges.


📦 Installation

Homebrew

brew tap getplumber/plumber
brew install plumber

To install a specific version:

brew install getplumber/plumber/plumber@0.1.52

Note: Versioned formulas are keg-only. Use the full path for example /usr/local/opt/plumber@0.1.52/bin/plumber or run brew link plumber@0.1.52 to add it to your PATH.

Mise

mise use -g github:getplumber/plumber

Requires mise activation in your shell, or run with mise exec -- plumber.

Binary Download

Linux (amd64)
curl -LO https://github.com/getplumber/plumber/releases/latest/download/plumber-linux-amd64
chmod +x plumber-linux-amd64
sudo mv plumber-linux-amd64 /usr/local/bin/plumber
Linux (arm64)
curl -LO https://github.com/getplumber/plumber/releases/latest/download/plumber-linux-arm64
chmod +x plumber-linux-arm64
sudo mv plumber-linux-arm64 /usr/local/bin/plumber
macOS (Apple Silicon)
curl -LO https://github.com/getplumber/plumber/releases/latest/download/plumber-darwin-arm64
chmod +x plumber-darwin-arm64
sudo mv plumber-darwin-arm64 /usr/local/bin/plumber
macOS (Intel)
curl -LO https://github.com/getplumber/plumber/releases/latest/download/plumber-darwin-amd64
chmod +x plumber-darwin-amd64
sudo mv plumber-darwin-amd64 /usr/local/bin/plumber
Windows (PowerShell)
Invoke-WebRequest -Uri https://github.com/getplumber/plumber/releases/latest/download/plumber-windows-amd64.exe -OutFile plumber.exe
Verify checksum
curl -LO https://github.com/getplumber/plumber/releases/latest/download/checksums.txt
sha256sum -c checksums.txt --ignore-missing

Docker

docker pull getplumber/plumber:latest

docker run --rm \
  -e GITLAB_TOKEN=glpat-xxxx \
  getplumber/plumber:latest analyze \
  --gitlab-url https://your-gitlab-instance.com \ 
  --project mygroup/myproject

Build from Source

Requires Go 1.24+ and Make.

git clone https://github.com/getplumber/plumber.git
cd plumber
make build # or make install to build and copy to /usr/local/bin/

🔍 CLI Reference

plumber analyze

Run compliance analysis on a GitLab project.

plumber analyze [flags]

Flags

Flag Required Default Description
--gitlab-url No* auto-detect GitLab instance URL
--project No* auto-detect Project path (e.g., group/project)
--config No .plumber.yaml Path to config file
--threshold No 100 Minimum compliance % to pass (0-100)
--branch No 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 1)
--verbose, -v No false Enable verbose/debug output for troubleshooting

* Auto-detected from git remote (origin) if not specified. Supports both SSH and HTTPS remote URLs.

Environment Variables

Variable Required Description
GITLAB_TOKEN Yes 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.

Automatic Version Check

When running locally, Plumber checks GitHub for newer releases on every invocation and prints an upgrade notice if one is available. The check runs asynchronously and has a 3-second timeout, so it never slows down the analysis.

The check is automatically skipped when:

  • Running in CI environments (CI or GITLAB_CI environment variables are set)
  • Using a development build (version is dev)

To disable it manually, set PLUMBER_NO_UPDATE_CHECK:

export PLUMBER_NO_UPDATE_CHECK=1

Exit Codes

Code Meaning
0 Compliance ≥ threshold
1 Compliance < threshold or error

plumber config generate

Generate a default .plumber.yaml configuration file.

plumber config generate [flags]
Flag Default Description
--output, -o .plumber.yaml Output file path
--force, -f false Overwrite existing file

Examples:

# Generate default config
plumber config generate

# Custom filename
plumber config generate --output my-plumber.yaml

# Overwrite existing conf file
plumber config generate --force

plumber config view

Display a clean, human-readable view of the effective configuration without comments.

plumber config view [flags]
Flag Default Description
--config, -c .plumber.yaml Path to configuration file
--no-color false Disable colorized output

Booleans are colorized for quick scanning: true in green, false in red. Color is automatically disabled when piping output.

Examples:

# View the default .plumber.yaml
plumber config view

# View a specific config file
plumber config view --config custom-plumber.yaml

# View without colors (for piping or scripts)
plumber config view --no-color

plumber config validate

Validate a configuration file for correctness. Detects unknown control names and sub-keys with typo suggestions using fuzzy matching.

plumber config validate [flags]
Flag Default Description
--config, -c .plumber.yaml Path to configuration file
--fail-warnings false Treat configuration warnings as errors (exit 1)

Warnings are printed to stderr so they don't interfere with scripted output. Use --fail-warnings to exit with code 1 when warnings are found (useful in CI).

Examples:

# Validate the default .plumber.yaml
plumber config validate

# Validate a specific config file
plumber config validate --config custom-plumber.yaml

# Fail on warnings (for CI pipelines)
plumber config validate --fail-warnings

Sample output with typos:

Configuration validation warnings:
  - Unknown control in .plumber.yaml: "containerImageMustNotUseForbiddenTag". Did you mean "containerImageMustNotUseForbiddenTags"?
  - Unknown key "tag" in control "containerImageMustNotUseForbiddenTags". Did you mean "tags"?
  - Unknown key "allowForcePushes" in control "branchMustBeProtected". Did you mean "allowForcePush"?

⚠️ Self-Hosted GitLab

If you're running a self-hosted GitLab instance, you'll need to host your own copy of the component.

There are two ways to bring Plumber to your self-hosted instance. Choose the one that fits your workflow:

Option A: Direct Import (simplest)

Import the upstream repository directly into your GitLab instance.

Step 1: Import the repository

  • Go to New Project → Import project → Repository by URL
  • URL: https://gitlab.com/getplumber/plumber.git
  • Choose a group/project name (e.g., infrastructure/plumber)

Step 2: Enable CI/CD Catalog

  • Go to Settings → General
  • Make sure the project has a description (required for CI/CD Catalog)
  • Expand Visibility, project features, permissions
  • Toggle CI/CD Catalog resource to enabled
  • Click Save changes

Step 3: Publish a release

The imported project comes with upstream tags. The preferred method is to run a pipeline on an existing tag to trigger the release:

  • Go to CI/CD → Pipelines → Run pipeline
  • Select an imported tag (e.g., v0.1.28) from the branch/tag dropdown
  • Click Run pipeline: this creates a release for that tag in the CI/CD Catalog

Alternatively, create a new tag manually, but this might conflict later on when you want to fetch remote tags:

  • Go to Code → Tags → New tag
  • Enter a version (e.g., 1.0.0)
  • Click Create tag

Step 4: Create a GitLab Token

In the project you want to scan:

  1. Go to User Settings → Access Tokens on your GitLab instance
  2. Create a Personal Access Token with read_api + read_repository scopes (or api if using mr_comment or badge)
    • Project Access Tokens also work: create one inside your project Settings → Access Tokens with the same scopes and at least Maintainer role
  3. Go to the project's Settings → CI/CD → Variables
  4. Add the token as GITLAB_TOKEN (masked recommended)

⚠️ The token must belong to a user (or project bot) with Maintainer role (or higher) on the project.

Step 5: Use in your pipelines

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.example.com/infrastructure/plumber/plumber@v0.1.28
    # inputs:
    #   stage: .pre | by default runs in .pre which only runs if there is at least another CI job in another stage

To update: re-import or manually pull upstream changes.

Option B: Fork on gitlab.com + Mirror (recommended)

Fork the project on gitlab.com first, then set up a pull mirror on your self-hosted instance. This way whenever you fetch upstream changes in your fork, your self-hosted mirror stays in sync automatically.

Step 1: Fork on gitlab.com

  • Go to getplumber/plumber on gitlab.com
  • Click Fork and create a fork under your gitlab.com namespace (e.g., your-org/plumber)

Step 2: Create a mirrored project on your self-hosted instance

  • On your self-hosted GitLab, go to New Project → Import project → Repository by URL
  • URL: https://gitlab.com/your-org/plumber.git
  • Choose a group/project name (e.g., infrastructure/plumber)

Step 3: Set up pull mirroring

  • In your self-hosted project, go to Settings → Repository → Mirroring repositories
  • Add the mirror URL: https://gitlab.com/your-org/plumber.git
  • Direction: Pull
  • Authentication: add a gitlab.com token with read_repository scope if the fork is private
  • Click Mirror repository

💡 Pull mirroring syncs automatically (every 30 minutes on GitLab Premium, or manually on other tiers). When upstream releases a new version, sync your fork on gitlab.com first, then your self-hosted mirror picks it up.

Step 4: Enable CI/CD Catalog

  • Go to Settings → General
  • Make sure the project has a description (required for CI/CD Catalog)
  • Expand Visibility, project features, permissions
  • Toggle CI/CD Catalog resource to enabled
  • Click Save changes

Step 5: Publish a release

The mirrored project comes with upstream tags. The preferred method is to run a pipeline on an existing tag to trigger the release:

  • Go to CI/CD → Pipelines → Run pipeline
  • Select an imported tag (e.g., v0.1.28) from the branch/tag dropdown
  • Click Run pipeline: this creates a release for that tag in the CI/CD Catalog

Alternatively, create a new tag manually:

  • Go to Code → Tags → New tag
  • Enter a version (e.g., 1.0.0)
  • Click Create tag

Step 6: Create a GitLab Token

In the project you want to scan:

  1. Go to User Settings → Access Tokens on your GitLab instance
  2. Create a Personal Access Token with read_api + read_repository scopes (or api if using mr_comment or badge)
    • Project Access Tokens also work: create one inside your project Settings → Access Tokens with the same scopes and at least Maintainer role
  3. Go to the project's Settings → CI/CD → Variables
  4. Add the token as GITLAB_TOKEN (masked recommended)

⚠️ The token must belong to a user (or project bot) with Maintainer role (or higher) on the project.

Step 7: Use in your pipelines

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.example.com/infrastructure/plumber/plumber@v0.1.28
    # inputs:
    #   stage: .pre | by default runs in .pre which only runs if there is at least another CI job in another stage

🔧 Troubleshooting

Issue Solution
GITLAB_TOKEN environment variable is required Set GITLAB_TOKEN in CI/CD Variables or export it locally
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 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 Use absolute path in Docker, relative path otherwise
Plumber job not running The 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 your .gitlab-ci.yml to prevent duplicate branch + MR pipelines (see Quick Start)
Plumber job skipped on branch The component only runs on merge request events, the default branch, and tags. Open an MR or push to the default branch to trigger it

💡 Need help? Open an issue or join our Discord


🤝 Contributing

Contributions are welcome! Please read our Contributing Guide for details on how to submit pull requests, report issues, and coding conventions.


💡 See it in action

Check out our example projects:


📰 Blog Posts & Articles

English

Français

📄 License

Mozilla Public License 2.0 (MPL-2.0)

Star History

Star History Chart

About

Open source CLI to check compliance of your GitLab CI/CD pipelines and repos

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages