# Plumber scans your CI pipeline files for the mistakes that leak credentials

> Plumber is a Go scanner for GitHub Actions and GitLab CI that looks for risky patterns in workflow definitions and repository settings, reports findings in the terminal or as SARIF, JSON, CycloneDX and other formats, and can run as an official GitHub Action on every pull request. It suits teams whose largest attack surface is the pipeline rather than the application code.

**getplumber/plumber** — Plumber detects CI/CD security issues in your GitHub workflows and gives you a score

- Repository: https://github.com/getplumber/plumber
- Website: https://getplumber.io
- Stars: 829 · Forks: 35
- Language: Go
- License: MPL-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/getplumber-plumber

## The pipeline is the attack surface

Plumber describes itself as a CI/CD security scanner for GitHub Actions and GitLab CI, and the description says it detects CI/CD security issues in your GitHub workflows and gives you a score. The scope is narrow and deliberate. It scans for risky patterns and security gaps in the definition of your pipelines, not in your application dependencies or your source. That focus follows from where the risk actually sits. A workflow file is a small YAML document that usually runs with elevated permissions, can pull in third party actions, and often carries tokens with wide scope, so a mistake there is often worse than a mistake in the code it builds. The README also points to Plumber Radar, a page of 20k public repos scanned, which is the project's way of showing the shape of the problem at scale.

## Workflow files and repository settings

What gets read is specific, and the difference between the two providers is worth noting. For GitHub Actions, Plumber scans `.github/workflows/*.{yml,yaml}` and the repository settings. For GitLab CI, it scans `.gitlab-ci.yml`, the resolved includes, and the repository settings. The inclusion of repository settings is what separates this from a lint of YAML syntax. A workflow file can be perfectly formed and still be dangerous in the context of the repository it lives in, because settings such as the permissions available to actions, who can approve a run, or what an untrusted pull request is allowed to do determine how much damage a bad workflow can cause. The repository layout mirrors the split, with `github/` and `gitlab/` directories alongside `provider/` for the abstraction over the two.

## Findings in eight output shapes

The reporting side is unusually broad. The README lists the formats findings are reported in as the terminal, JSON, SARIF, GitLab SAST, CSV, OCSF, PBOM and CycloneDX. Each of those answers a different question. The terminal output is for the person running it, SARIF is what code scanning ingests so that findings appear inline on a pull request, GitLab SAST is the equivalent for the merge request widget, and OCSF is the open standard other security tooling can consume. PBOM and CycloneDX are the software bill of materials formats, so the tool can also emit an inventory of what a pipeline pulls in, and CSV is there for the spreadsheets. The repository has a `reports/` directory and a `pbom/` directory, and the CLI is built on cobra, with lipgloss and termenv for the terminal rendering.

## Policies come from configuration and an OPA layer

The rule set is data rather than hard coded logic, and the dependency list shows how. The module requires `github.com/open-policy-agent/opa` at version 1.21.2, which is the Open Policy Agent, and the repository has a `policies/` directory alongside `defaultConfig/`, which holds the built in `.plumber.yaml` that the CLI runs with when you give it no configuration of your own. The other directories in the tree describe the rest of the pipeline: `capability/` for what the scanner can do, `finding/` for the findings themselves, `control/` for the controls, `templates/` for output templates, `configuration/` for configuration handling, `cmd/` for the command line, and `utils/` and `internal/` for the plumbing. A YAML configuration file and a policy engine together mean the rules can be tuned per organisation, which is the difference between a scanner you adopt and a scanner you argue with.

## Four installation routes, and how upgrades behave

The quick start is a Homebrew install and one command:

```bash
brew tap getplumber/plumber
brew trust --formula getplumber/plumber/plumber
brew install plumber

plumber analyze
```

No configuration file is needed, because `plumber analyze` runs with the built in default configuration and auto detects the provider from your git remote, which is a small detail that removes the most common first run failure. There are three other routes. mise with `mise use -g github:getplumber/plumber`, a binary from GitHub Releases, or the Docker image `docker.io/getplumber/plumber` on Docker Hub. The upgrade behaviour is spelled out, and it is unusual enough to know. A mise install pins the version it resolved at install time, so an upgrade is an explicit step, and mise also ignores a release younger than 24 hours by default through `minimum_release_age`, so a release of the day arrives the next day unless you set `MISE_MINIMUM_RELEASE_AGE=0`. `plumber version` prints an upgrade notice with the command for your install whenever a newer release exists.

## Authenticating and choosing the target

Two providers, two authentication styles, and the README gives both. For GitHub you can use the gh CLI keyring with `gh auth login`, or for CI runners and automation set `GH_TOKEN`. For GitLab, `GITLAB_TOKEN` is the variable. The command shape is the same in both cases, and the target is flexible:

```bash
# current repo
plumber analyze

# a GitHub repo without a local clone
plumber analyze github.com/owner/repo

# a GitLab project without a local clone (self-hosted instances work too)
plumber analyze gitlab.com/group/project
```

The ability to point at a repository you have not cloned is what makes the tool usable from a script, and the note that self-hosted GitLab instances work extends that to installations the public service does not cover. A pasted browser URL also works, and the README notes that a URL with a tree path selects that branch, which saves an argument when you want to check a specific ref.

## The GitHub Action and the score it publishes

The official action is added to a workflow file, and the permissions it asks for are worth reading closely because they define what the scanner can see:

```yaml
name: Plumber

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  actions: read # lets Plumber read workflow states and one run log to prove the token scope
  security-events: write
  id-token: write # required by score-push

jobs:
  plumber:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v6
      - uses: getplumber/plumber@<version>
        with:
          score-push: true
```

The `actions: read` permission is there so Plumber can read workflow states and one run log to prove the token scope, and `id-token: write` is required by the score push step. The `score-push` option publishes your Plumber Score and your repository name publicly on score.getplumber.io, and the comment says to set it to false to keep them private. The repository also ships a GitLab CI component, and the README has badges for the score, the OpenSSF Scorecard and an SLSA Level 3 claim.

## MPL-2.0, a fast release line, and what the score is for

The licence is MPL-2.0, the file level copyleft licence, which is a reasonable choice for a tool whose rules and configuration people are likely to extend and redistribute. The release cadence is fast: v0.7.7, v0.7.8 and v0.7.9 were all published on 2026-10-19, and the last push to the repository came a few days later, with the project not archived. The build pipeline is careful in the ways that matter for a security tool. The Dockerfile pins the Go base image by digest to a build shipping a specific Go patch release, with comments naming the standard library advisories that the bump addresses, and it forces `GOTOOLCHAIN=auto` so that a toolchain bump in go.mod is honoured rather than silently using the base image's standard library. The Makefile mirrors CI with `golangci-lint`, a `go test./...` target and a whole program dead code check with documented exemptions. A tool that tells you your pipeline is insecure should be held to a similar standard, and here at least the plumbing is visible.

## Conclusion

Adopt Plumber when your workflow definitions and repository settings are the thing you most want audited, since it reads GitHub Actions files and GitLab CI files including resolved includes, and it drops findings straight into code scanning through SARIF. Do not adopt it as a general application security scanner or a dependency scanner, because the README scopes it to CI/CD pipelines. Verify first which findings your own policy set produces, since the rules come from a configuration file and a policy layer, and decide early whether to keep the Plumber Score public, because the action's `score-push` option publishes your score and repository name on score.getplumber.io unless you set it to false.

## FAQ

### What does Plumber scan?

For GitHub Actions it scans `.github/workflows/*.{yml,yaml}` and the repository settings, and for GitLab CI it scans `.gitlab-ci.yml`, the resolved includes and the repository settings. The scope is CI/CD pipelines, not application code or dependencies.

### How do I install Plumber?

With Homebrew using `brew tap getplumber/plumber`, `brew trust --formula getplumber/plumber/plumber` and `brew install plumber`, or with `mise use -g github:getplumber/plumber`, a binary from GitHub Releases, or the Docker image `docker.io/getplumber/plumber`.

### Does Plumber need a configuration file?

No. `plumber analyze` runs with the built in default configuration at `defaultConfig/.plumber.yaml` and auto detects the provider from your git remote.

### Does the GitHub Action publish my repository publicly?

It can. The `score-push` option publishes your Plumber Score and repository name publicly on score.getplumber.io, and the workflow comment says to set it to false to keep them private.

## Sources

- [getplumber/plumber on GitHub](https://github.com/getplumber/plumber)
- [License: MPL-2.0](https://github.com/getplumber/plumber/blob/main/LICENSE)
- [Project website](https://getplumber.io)
- [README](https://github.com/getplumber/plumber/blob/main/README.md)
- [Releases](https://github.com/getplumber/plumber/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/getplumber-plumber
