Skip to content

[FEAT] New control: mrApprovalMustBeRequired — enforce MR approval rules #85

@stephrobert

Description

@stephrobert

Is your feature request related to a problem? Please describe.

Without mandatory MR approvals, an attacker can modify the .gitlab-ci.yml or any script executed by the pipeline (Direct/Indirect PPE), push it, and the pipeline will execute the malicious code before any human review. This is the root cause of two major CI/CD attack vectors:

  • Direct PPE: Attacker modifies .gitlab-ci.yml on a branch → pipeline runs with secrets
  • Indirect PPE: Attacker modifies a script called by the pipeline (e.g., scripts/run-tests.sh) via a MR → pipeline executes the modified version before approval

The Protection data collector already retrieves MRApprovalRules, MRApprovalSettings, and MRSettings from the GitLab API, but no control currently exploits this data.

Describe the solution you'd like

Add a new control mrApprovalMustBeRequired that verifies:

  1. MR approvals are required (approvalsBeforeMerge >= N)
  2. Authors cannot approve their own MR (mergeRequestsAuthorApproval == false)
  3. Approvals reset on new push (resetApprovalsOnPush == true) — prevents approved MR from being modified after approval
  4. Committers cannot approve (mergeRequestsDisableCommittersApproval == true)

Configuration in .plumber.yaml

controls:
  mrApprovalMustBeRequired:
    enabled: true
    # Minimum number of approvals required
    minApprovals: 1
    # Author must not be able to approve their own MR
    preventAuthorApproval: true
    # Approvals must reset when new commits are pushed
    resetApprovalsOnPush: true
    # Committers must not be able to approve
    preventCommitterApproval: true

Implementation Hints

These are just ideas. Feel free to change the implementation.

  1. Data already collected: The collector/dataCollectionGitlabProtection.go already fetches MRApprovalRules, MRApprovalSettings, and MRSettings via the GitLab API. This data is passed to controls via GitlabProtectionData.
  2. New control file: Create control/controlGitlabProtectionMRApproval.go.
  3. Logic: Compare the project's MR approval settings against the configured requirements. Each sub-check that fails reduces compliance proportionally.
  4. Compliance calculation: Each enabled sub-check that passes contributes equally. For example, if 4 checks are enabled and 3 pass, compliance = 75%.
  5. Integration: Wire in task.go alongside the existing branchMustBeProtected control (they share the same Protection data collector).

Files Touched

  • control/controlGitlabProtectionMRApproval.go (new control)
  • control/types.go (add result field to AnalysisResult)
  • control/task.go (wire the new control, reuse existing Protection data collection)
  • configuration/plumberconfig.go (add config struct and getter)
  • .plumber.yaml (add default config section)
  • cmd/analyze.go (add output formatting)

Why It's Valuable

MR approval enforcement is a foundational security control that prevents both Direct and Indirect PPE attacks. The data is already being collected by plumber but not used — this control would unlock that existing investment with minimal effort. It complements branchMustBeProtected by covering the merge request workflow rather than just branch-level protections.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions