Controls
Plumber checks your CI/CD configuration and project settings against a catalog of controls. The catalog is split per provider: GitLab controls inspect .gitlab-ci.yml and GitLab project settings, GitHub controls inspect .github/workflows/*.yml and GitHub repository settings. Pick the provider tab below to see the relevant catalog.
When a control is not respected, an issue is created. Click the ISSUE-XXX link in any row to see the full description, impact, and remediation for that issue.
Scope
- All: Plumber Platform and Open Source CLI
- Platform: Plumber Platform only
- CLI: Open Source CLI only (not a Platform control)
CI/CD provider
CI/CD Container Images
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| All | Container images must come from authorized sources ISSUE-101 | Verifies that container images used to run your CI/CD pipelines come from authorized and trusted sources. | Helps mitigate security risks introduced by the use of malicious, compromised, or vulnerable images. |
| All | Container images must not use forbidden tags ISSUE-102 | Verifies that container images used to run your CI/CD pipelines rely on authorized tags. | Helps mitigate both security and functional risks introduced by the use of unverified, outdated, or compromised image versions. |
| CLI | Container images must be pinned by digest ISSUE-103 | Verifies that container images are referenced by their SHA256 digest rather than a mutable tag. | Prevents supply chain attacks where a tag is overwritten with a compromised image, guaranteeing exact image content in every pipeline run. |
CI/CD Variables
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| Platform | CI/CD variables must be protected ISSUE-201 | Verifies that CI/CD variables used in a project have the protected field enabled. | Ensures sensitive values are restricted to protected branches or tags, reducing unauthorized exposure. |
| Platform | CI/CD variables must be masked ISSUE-202 | Verifies that CI/CD variables used in a project have the masked field enabled. | Prevents variable values from being exposed in pipeline logs, reducing the risk of leaks. |
| CLI | Pipeline must not enable debug trace ISSUE-203 | Verifies that CI_DEBUG_TRACE and CI_DEBUG_SERVICES are not enabled in the pipeline configuration. | Prevents exposure of all CI/CD variable values in job logs, including secrets and tokens. |
| CLI | Pipeline must not use unsafe variable expansion ISSUE-204 | Detects user-controlled CI variables expanded in shell re-interpretation contexts (eval, sh -c/bash -c, envsubst or xargs piped into a shell, source/. sourcing). | Prevents command injection via crafted branch names, MR titles, or commit messages (OWASP CICD-SEC-1). |
| CLI | 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. | Reduces risk of tampering with scanner settings or other governed variables via pipeline YAML. |
Pipeline Composition
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| All | Pipeline must not contains hardcoded jobs ISSUE-401 | Verifies that no hardcoded job is used in CI/CD pipelines. | Improves maintainability and keeps pipelines aligned with best practices. |
| CLI | Includes must not use ambiguous tag/branch refs ISSUE-402 | Flags include: refs (project ref: or CI/CD component @version) that resolve upstream as both a tag and a branch. API-backed: stays silent when the upstream probe cannot run. | A tag deletion or rename can silently rebind the include to a mutable branch, swapping the revision that runs. Pin by commit SHA. |
| All | Pipeline must use only up-to-date includes ISSUE-403 | Verifies that the included pipelines are up-to-date compared to their source. | Reduces risks from outdated or vulnerable templates. |
| All | Pipeline must not use forbidden ref in includes ISSUE-404 | Verifies that the included refs are using specified tags. | Prevents reliance on insecure or unapproved references. |
| All | Pipelines must include templates ISSUE-405ISSUE-406 | Verifies that the projects contain specific templates. This control can also allow overriding certain variables in the included templates. | Ensures pipelines follow the security practices your policy requires. |
| Platform | Pipeline must include required phases ISSUE-407 | Verifies that the CI/CD pipeline includes a group of job types. | Ensures the pipeline execution flow is complete and matches your policy. |
| All | Pipelines must include components ISSUE-408ISSUE-409 | Verifies that the projects contain specific GitLab components. This control can also allow overriding certain variables in the included components. | Ensures pipelines integrate mandatory security steps. |
| CLI | Security jobs must not be weakened ISSUE-410 | Detects security scanning jobs (SAST, Secret Detection, Container Scanning, etc.) weakened by allow_failure: true, rules: overrides with when: never / when: manual, or when: manual at job level. | Prevents silently neutralized security scans that give a false sense of security (OWASP CICD-SEC-4). |
| CLI | Pipeline must not execute unverified scripts ISSUE-411 | Detects jobs that download and immediately execute scripts from the internet (curl | bash, wget | sh, download-then-execute, base64 pipe-to-shell) without integrity verification. | Prevents supply chain attacks where a compromised URL serves a modified script that exfiltrates secrets (OWASP CICD-SEC-3, CICD-SEC-8). |
| CLI | Pipeline must not use Docker-in-Docker ISSUE-412ISSUE-413 | Detects Docker-in-Docker (docker:dind) services and insecure daemon configuration (for example plaintext Docker API). | Reduces container escape and lateral movement risk on shared runners; prefer Kaniko or Buildah for image builds. |
Access and Authorization
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| All | Branch must be protected ISSUE-501ISSUE-505 | Verifies that the project configuration respects the protection, push, merge and owner approval on included branch names. | Prevents unauthorized modifications and enforces branch protection standards. |
| Platform | MR approval rules must have at least N approvals required ISSUE-502 | Verifies that the project merge request approval rules that cover all protected branches have a minimum number of approval requirements. | Prevents unreviewed code from being merged, reducing security risks. |
| Platform | MR approval settings must be compliant ISSUE-503 | Verifies that MR approval settings are properly configured. | Ensures review and security requirements are enforced. |
| Platform | An MR approval rule must be defined to cover all protected branches ISSUE-504 | Verifies that the protected branches have at least one approval rule. | Ensures protected branches cannot bypass review processes. |
| Platform | MR settings must be compliant ISSUE-506 | Verifies that the project's merge request settings are correct in terms of merge method, resolving differences, squashing, etc. | Reduces risk of unauthorized or insecure code changes. |
| Platform | Number of project members must respect a quota ISSUE-507 | Verifies that the project configuration respects the owner, maintainer and developer quotas. | Prevents uncontrolled access that could weaken project security. |
| Platform | Number of group members must respect a quota ISSUE-508 | Verifies that the group configuration respects the owner, maintainer and developer quotas. | Prevents uncontrolled access at group level, strengthening governance. |
Security Source
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| Platform | Project must have a security policy source ISSUE-601 | Verifies if the projects have a specific project as their source of security policy. | Ensures the security policy is applied and reduces the risk of unmanaged vulnerabilities. |
GitHub Actions support is currently Open Source CLI only.
All controls below carry the CLI badge. Plumber Platform does not scan GitHub Actions workflows yet; Platform GitHub support follows in a later release.
What Plumber scans (GitHub CLI)
- Source: committed files under
.github/workflows/in the analyzed ref (local clone or remote fetch). - Static YAML only: no org/repo Variables, no runtime
$GITHUB_ENVwrites, no evaluation of GitHub expression syntax in values. - Step
uses:third-partyowner/repo@refactions (ISSUE-702/703). Not local./.github/actions/*, not job-level reusable-workflowuses:, not nested actions inside composites. - API-backed rules (ISSUE-702, ISSUE-703): need
gh auth loginorGH_TOKEN. Without auth they abstain (no finding, not a pass).
CI/CD Container Images
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| All | Container images must not use forbidden tags ISSUE-102 | Same forbidden-tag policy (latest, dev, …), applied to GitHub Actions container references. With the embedded option containerImagesMustBePinnedByDigest: true, any non-digest reference also fails. | Mutable tags create non-reproducible builds and a path for surprise upgrades. |
CI/CD Variables
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| CLI | Pipeline must not enable debug trace ISSUE-203 | Static env: (workflow/job/step, merged per job): truthy literals plus GitHub expressions on forbidden names. Also run: lines that write forbidden names to $GITHUB_ENV. | Debug toggles dump masked secrets into logs. Org/repo Variables with no YAML reference and UI “Re-run with debug logging” remain out of scope. |
| CLI | Workflow must not inline user input into shell scripts ISSUE-207 | Detects attacker-controlled free-text expressions (github.event.* titles, bodies, branch names, commit messages, fork metadata, author identity, plus github.head_ref) inlined directly into run: shell scripts. Numeric, boolean, enum and SHA fields are deliberately not flagged. | Workflow template-injection is the GitHub Actions equivalent of SQL injection: the PR title or branch name becomes part of the executed shell command. |
| CLI | Workflow must not write untrusted content to $GITHUB_ENV ISSUE-209 | Flags a run: step where attacker-controlled free text (same field list as ISSUE-207) reaches $GITHUB_ENV or $GITHUB_PATH: inlined directly, dereferenced from an env:-bound shell variable, or written through a heredoc body (sinks: >>, >, tee). | Both files are sticky: every later step inherits the variable or PATH entry. An attacker-controlled value can set NODE_OPTIONS=--require=./exfil.js or front-load a malicious PATH directory and hijack later steps. Worst under pull_request_target / workflow_run, where the run carries secrets. |
CI/CD Secrets
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| CLI | Reusable workflow must not use `secrets: inherit` ISSUE-302 | Flags callers that pass secrets: inherit instead of declaring the secrets the callee needs. | Inheriting hands every caller secret to the reusable workflow, so a third-party reusable workflow sees secrets it should never see. |
| CLI | Workflows must not expose all secrets at once ISSUE-309 | Flags a job that serialises the whole secrets context with toJson(secrets) or toJSON(secrets) into a run: script, an env: binding, or an action with: input. Scoped references like ${{ secrets.NPM_TOKEN }} are the intended pattern and stay silent. | GitHub's log redaction masks known secret values, not a JSON blob derived from them, so a single echo of the dump leaks every repository, organisation and environment secret the job can see, and a compromised reusable action gets them directly. Pass each secret by name instead. |
Pipeline Composition
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| CLI | Actions must not use ambiguous tag/branch refs ISSUE-402 | Flags uses: owner/repo@ref references that resolve upstream as both a tag and a branch. API-backed: stays silent when the upstream probe cannot run. | A tag deletion, rename, or typo can silently switch which revision runs. Pin by 40-char commit SHA. |
| CLI | Security jobs must not be weakened ISSUE-410 | Detects security scanning jobs (glob-matched via securityJobPatterns) that set continue-on-error: true, so scan failures never fail the pipeline. | Same OWASP CICD-SEC-4 pattern as on GitLab: a green pipeline that secretly skipped its security scan is worse than no scan. |
| CLI | Workflow must not execute unverified scripts ISSUE-411 | Detects workflow steps that download and immediately execute scripts (curl | bash, wget | sh, download-then-execute, base64 pipe-to-shell) without integrity verification. | Blocks Megalodon-style supply-chain execution in GitHub Actions: a compromised URL or obfuscated inline payload runs with the job's GITHUB_TOKEN and secrets (OWASP CICD-SEC-3, CICD-SEC-8). |
| CLI | Workflow must not use Docker-in-Docker ISSUE-412ISSUE-413 | Detects DinD service usage and insecure daemon configuration in workflow jobs. | Container escape and lateral movement risk applies to self-hosted runners the same way it does to GitLab shared runners. |
| CLI | Workflows must include required actions ISSUE-417 | Asserts that every workflow file under .github/workflows/ collectively references a configured set of required actions or reusable workflows. Step-level uses: and job-level reusable-workflow uses: both count. Matching is ref-agnostic with a slash-guard against accidental prefix collisions. | Closes the gap between an org-wide policy that says "every repo runs the security suite" and a per-repo workflow that quietly dropped the uses: line. Disabled by default; opt in once the required action set is settled. |
Access and Authorization
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| All | Branch must be protected ISSUE-501ISSUE-505 | Reads both classic Branch Protection and any Repository or Organization Ruleset covering the branch. Verifies required reviews, force-push prevention, code-owner approval, and status checks against the merged effective configuration (stricter wins). | Prevents direct push and force-push attacks on protected refs even when half the protection lives in classic Branch Protection and the other half in a Ruleset. |
Third-party actions
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| CLI | Third-party actions must be pinned by commit SHA ISSUE-701 | Verifies step-level uses: references and job-level reusable-workflow calls are pinned by a 40-character commit SHA, not a mutable tag or branch. Local ./… actions and trustedOwners (default: actions, github) are exempt. | Defends against the March 2025 tj-actions / reviewdog supply chain compromise pattern (CVE-2025-30066), where attackers retag actions to point at malicious code that then runs with the caller's secrets. |
| CLI | Actions must not reference archived repositories ISSUE-702 | Step-level uses: owner/repo@ref in committed workflows. Queries GitHub for archived: true (one cached API call per owner/repo). Requires gh / GH_TOKEN; abstains without auth. | Archived repos stop receiving patches. Does not inspect reusable-workflow callees, local actions, or runtime installs. PBOM: archived: true. |
| CLI | Actions must not carry known CVEs ISSUE-703 | Same step-level uses: scope as ISSUE-702. Queries GitHub Advisory Database (actions ecosystem); semver-filters by vulnerable_version_range when the ref resolves to a release version. Refs that cannot be resolved (a SHA with no release tag, a mutable non-numeric tag) abstain with a 'could not verify' warning. | Catches known-vulnerable action versions (e.g. tj-actions CVE-2025-30066). Requires API auth; abstains without it. PBOM: hasCve + advisories: [GHSA-…]. |
| CLI | Release workflows must not restore an untrusted cache ISSUE-705 | Flags a release or publish job that restores a build cache (actions/cache, actions/cache/restore, or a setup-* action's built-in cache) with a key not scoped to the release ref. Release context is a release trigger or a canonical publish action such as pypa/gh-action-pypi-publish or softprops/action-gh-release. | Actions caches are shared across branches with a permissive fallback, so a PR run on any feature branch can populate the same key a release job later restores, injecting compromised artefacts into the published package. Weave github.ref_name or github.sha into the key, or disable caching on publish paths. The May 2026 TanStack attack used this exact fallback. |
| CLI | Pinned Upstream Actions must exist ISSUE-707 | Flags a step pinned to a 40-character commit SHA that the upstream repository confirms does not exist. Owner-agnostic (first- and third-party alike); non-SHA refs, local ./… actions, and docker:// images are out of scope. Requires gh / GH_TOKEN. | A SHA absent upstream is either a typo (the runner falls back to the default branch) or a commit that was removed or never pushed upstream, so the pin resolves nowhere. Fires only when the API confirms the commit is absent from a readable repo; a SHA it cannot verify (private repo, rate limit, network) stays silent, so a valid pin is never mislabeled. It does not detect a fork-network impostor commit (one pushed to a fork of the upstream repo), which GitHub serves as present from the parent. |
| CLI | Actions must come from authorized sources ISSUE-713 | Restricts every step-level uses: and job-level reusable-workflow uses: to authorized sources: GitHub-official owners (actions/*, github/*), the scanned repo's own org (trustSameOrgActions, default on), a trustedGithubActions allowlist (exact owner/repo or owner/* wildcard), or a minimumStars popularity floor. | Third-party actions run with the caller's GITHUB_TOKEN and secrets, so an unvetted owner is a direct supply-chain entry point. The star floor also catches rename/re-creation namespace squats. Local ./… and docker:// refs are exempt; minimumStars needs API auth and falls back to the allowlist without it. |
Workflow triggers and permissions
| Scope | Control | What it checks | Why it matters |
|---|---|---|---|
| CLI | Workflow permissions must be declared ISSUE-801 | Flags each job whose effective permissions are undeclared: no job-level permissions: block and no workflow-level one to fall back to, so the GITHUB_TOKEN keeps the repository default. | Implicit permissions default to whatever the repo / org allows, often write-all. Explicit minimum scopes shrink blast radius on a token leak. |
| CLI | Workflow must not use dangerous triggers ISSUE-802 | Flags jobs that combine a secret-bearing, fork-influenceable trigger (workflow_run, issue_comment, pull_request_review, pull_request_review_comment, discussion, discussion_comment, gollum, fork) with a checkout of fork-controlled code and no same-repo or trusted-author guard. The trigger alone is never flagged; pull_request_target is owned by ISSUE-804. | These triggers run with the base repository's secrets while unprivileged users can fire them; add a fork-controlled checkout and attacker code executes with those secrets (the March 2025 tj-actions vector, CVE-2025-30066). |
| CLI | Job must not run with write-all permissions ISSUE-803 | Static permissions: in workflow YAML. Flags only the literal shortcut write-all (workflow-level propagates to every job). Not read-all, not scope maps like contents: write, not missing blocks (ISSUE-801). | write-all grants every GITHUB_TOKEN scope at once. Pair with ISSUE-801 for jobs with no explicit block. |
| CLI | pull_request_target must not check out the PR head ISSUE-804 | Flags workflows on the pull_request_target trigger that check out the PR head (github.event.pull_request.head.sha or head_ref): the PR author's code then runs with the base repository's secrets and a write-scoped GITHUB_TOKEN. A job-level same-repository guard silences the finding, since fork code never runs. | The exact pattern behind the March 2025 tj-actions / reviewdog compromise: every step after the head checkout is a secret-exfiltration path. Prefer the pull_request trigger, which runs fork PRs without secret access. |