CI/CD provider
Branch protection missing
Control: Branch must be protected· Config key: branchMustBeProtected
📋 What is this?
A branch is not protected on the repository.
⚠️ Impact
Unprotected branches are highly vulnerable to unauthorized modifications. For instance, any member can push malicious code directly to your production branch without any review or validation.
🔧 How to fix
Enable branch protection on the branch to restrict changes to authorized users only.
# GitLab project settings: ❌ Branch not protected# Branch "main" has no protection rules# Anyone with Developer access can:# - Push directly# - Force push# - Delete the branch
# .plumber.yaml configuration requiring protection:branchMustBeProtected: enabled: true defaultMustBeProtected: true namePatterns: - main - release/*# GitLab project settings: ✅ Branch properly protected# Settings > Repository > Protected Branches:## Branch: main# Allowed to merge: Developers + Maintainers# Allowed to push: Maintainers# Allow force push: No# Code owner approval required: Yes
# .plumber.yamlbranchMustBeProtected: enabled: true defaultMustBeProtected: true namePatterns: - main - release/* allowForcePush: false codeOwnerApprovalRequired: true minMergeAccessLevel: 30 # Developer minPushAccessLevel: 40 # Maintainer💡 Tips
- Use
namePatternswith wildcards to protect branch families (e.g.,release/*). - Set
minPushAccessLevel: 40(Maintainer) to prevent developers from pushing directly. - Enable
codeOwnerApprovalRequiredif you use a CODEOWNERS file.
⚙️ Configuration
This control is configured in .plumber.yaml under the gitlab section:
gitlab:
controls:
branchMustBeProtected:
enabled: trueSee the CLI documentation for the full configuration reference. On Plumber Platform, the same key is used in your policy configuration.
Branch protection missing
Control: Branch must be protected· Config key: branchMustBeProtected
📋 What is this?
A branch matching the configured protection patterns has no protection rule in either classic Branch Protection or any Repository or Organization Ruleset that covers it. Plumber reads both mechanisms and treats a branch as protected only when at least one of them applies.
⚠️ Impact
Without protection on the matching branches, anyone with write access can push directly, force-push, rewrite history, or delete the branch. Required reviews, code-owner approvals, and status checks are all bypassed.
🔧 How to fix
This is a settings change in the GitHub web UI, not a code or .plumber.yaml change. The .plumber.yaml config only tells Plumber which branches to check. Protect the branch through either mechanism: a Repository Ruleset (recommended for new setups) or classic Branch Protection. Plumber reads both and merges them, so a rule defined in one is enough to satisfy the policy.
- 1
Open Settings > Rules > Rulesets and click New branch ruleset.
Rulesets are the modern mechanism and are recommended here. Prefer the classic UI instead? Use Settings > Branches > Add branch ruleset, where the settings below map one-to-one.
- 2
Name the ruleset and set Enforcement status to Active.
Disabled and evaluate-mode rulesets are ignored by Plumber; only an active ruleset counts as protection.
- 3
Under Target branches, add every branch this policy covers.
Match the branches in your
namePatterns(for examplemainandrelease/*). Use Include by pattern for wildcards likerelease/*. - 4
Enable Restrict deletions and Block force pushes.
This stops the branch from being deleted or having its history rewritten.
- 5
Enable Require a pull request before merging.
Set Required approvals to at least 1, tick Dismiss stale pull request approvals when new commits are pushed, and, if you use a
CODEOWNERSfile, Require review from Code Owners. - 6
Optionally enable Require status checks to pass and add your checks.
For example
build,test, andcodeql, so failing pipelines can't be merged. - 7
Click Create to save the ruleset.
Re-run Plumber to confirm the finding clears. The branch is now protected and the policy is satisfied.
💡 Tips
- Plumber needs
Administration: Read(fine-grained PAT) orreposcope (classic) to read protection rules at all. Without it the rule reportspartialControls(abstain), not a pass. - Classic Branch Protection and Rulesets are unioned. A code-owner-approval rule defined only in a Ruleset (no classic rule for the same branch) is still seen.
- Disabled or evaluate-mode rulesets are ignored automatically. Only enforced rulesets contribute to the effective configuration.
namePatternsaccepts glob patterns. Common:[main, master, release/*, v*.*.*].- Pair with ISSUE-505 for per-setting violations (force-push allowed, code-owner approvals missing, etc.).
⚙️ Configuration
This control is configured in .plumber.yaml under the github section:
github:
controls:
branchMustBeProtected:
enabled: trueSee the CLI documentation for the full configuration reference. On Plumber Platform, the same key is used in your policy configuration.