Skip to content

GitLab group governance

Where the gitlab provider audits one project's .gitlab-ci.yml, the gitlab_group provider audits the group-wide controls that govern every project in a GitLab group at once: whether two-factor authentication is required of all members, whether members can fork the group's projects outside the group, and the rest of the group-owner settings layer. It pulls GET /groups/{group} via the same GitLab REST v4 fetcher the scm provider's GitLab path uses. The GitLab analog of the GitHub-only scm_org provider.

The group-owner settings are only returned to a token with read_api and Owner access to the group; without one, or on any 404 / network error, each rule passes with an "unavailable" note rather than firing on absence, so a low-scope token never produces a false finding.

Producer workflow

# Token comes from --gitlab-token or $GITLAB_TOKEN (needs read_api + Owner).
pipeline_check --pipeline gitlab_group --scm-org my-group \
               --gitlab-token "$GITLAB_TOKEN"

What it covers

6 checks · 0 have an autofix patch (--fix).

Check Title Severity Fix
GLGRP-001 GitLab group does not require two-factor authentication HIGH
GLGRP-002 GitLab group allows forking projects outside the group MEDIUM
GLGRP-003 GitLab group allows sharing projects outside the group hierarchy MEDIUM
GLGRP-004 GitLab group default branch protection is disabled for new projects MEDIUM
GLGRP-005 GitLab group webhook delivers events over insecure transport HIGH
GLGRP-006 GitLab group CI/CD variable exposes a secret with a weak control HIGH

GLGRP-001: GitLab group does not require two-factor authentication

HIGH CICD-SEC-2 CWE-308

Reads require_two_factor_authentication from GET /groups/{group}. Fires when it is false. The field is only returned to a token with Owner access to the group (read_api); when it is absent the rule passes with an 'unavailable' note rather than guessing, so a low-scope token never produces a false finding. Group-wide 2FA is the single highest-leverage account-takeover control.

Recommended action

Turn on Require all users in this group to set up two-factor authentication (Group Settings -> General -> Permissions and group features). Without it, a single phished or reused member password is enough to push to the group's projects, approve merge requests, or run pipelines as that member. Enabling the requirement starts a grace period after which members without 2FA lose access, so set two_factor_grace_period and notify the group first. The GitHub-org analog is ORG-001.

GLGRP-002: GitLab group allows forking projects outside the group

MEDIUM CICD-SEC-2 CWE-200

Reads prevent_forking_outside_group from GET /groups/{group} and fires when it is false. true passes. The field is a Premium / Ultimate group setting; on a plan or token that does not return it the rule passes with an 'unavailable' note rather than guessing, so a low-scope token or free-tier group never produces a false finding.

Recommended action

Turn on Prevent project forking outside current group (Group Settings -> General -> Permissions and group features). When it is off, any member can fork a private or internal project to a namespace outside the group, where the group's branch protection, approval rules, and member 2FA policy no longer apply, and the copy persists after the member leaves. That moves source code outside the controls that govern the group, a data-exfiltration and IP-leak path that needs no exploit. The GitHub-org analog is ORG-007.

GLGRP-003: GitLab group allows sharing projects outside the group hierarchy

MEDIUM CICD-SEC-2 CWE-284

Reads prevent_sharing_groups_outside_hierarchy from GET /groups/{group} and fires when it is false (sharing outside the hierarchy is allowed). true passes. The setting is tied to Premium / SAML group features; on a plan or token that does not return it the rule passes with an 'unavailable' note rather than guessing, so a low-scope token or free-tier group never produces a false finding. Sits alongside GLGRP-002 (forking outside the group): both are group-level access-boundary controls.

Recommended action

Turn on Prevent members from sharing projects in this group with groups outside the hierarchy (Group Settings -> General -> Permissions and group features). When it is off, a member can share a private or internal project with a group outside the current hierarchy, granting that external group's members standing access to the project, outside the controls (branch protection, approval rules, 2FA policy, audit log) that govern this group. Restrict sharing to the hierarchy and grant external access only through reviewed, time-bound membership. The GitHub-org analog is ORG-007 (forking) / outside-collaborator policy.

GLGRP-004: GitLab group default branch protection is disabled for new projects

MEDIUM CICD-SEC-1 CWE-284

Reads default_branch_protection from GET /groups/{group} and fires when it is 0 (Not protected). 1-4 (partial / full / protected-against-push / full-after-initial-push) pass. GitLab is migrating this integer to a default_branch_protection_defaults object; when only that newer form is returned (the integer absent) the rule passes with an 'unavailable' note rather than guessing at the object's shape, so it never produces a false finding. This is the group-wide default for new projects; SCM-001 audits a specific repository's branch protection.

Recommended action

Raise the group's Default branch protection above Not protected (Group Settings -> General -> Permissions and group features). At 0 (Not protected), every new project created in the group starts with a default branch any Developer can push to directly, force-push, and delete, with no review gate, so a single compromised or careless member can rewrite history or ship unreviewed code on day one. Set it to at least Partially protected (no force-push) and prefer Fully protected so new projects inherit a safe default; individual projects can still tighten it further. The repo-level analog is SCM-001.

GLGRP-005: GitLab group webhook delivers events over insecure transport

HIGH CICD-SEC-6 CICD-SEC-10 CWE-319

Reads GET /groups/{group}/hooks and fires on any webhook whose url starts with http:// or whose enable_ssl_verification is false (an https endpoint with TLS verification off). Scoped to transport security: unlike the per-project SCM-026 it does not flag a missing secret token, because the group hooks endpoint does not report secret presence. Needs a token with read_api and Owner access to the group; when the endpoint is unavailable the rule passes with a note rather than firing on absence.

Recommended action

For each flagged group webhook (Group Settings -> Webhooks -> edit), switch the URL to https:// and enable SSL verification. A group webhook fires on events across every project in the group, so its payloads carry merge-request diffs, push commits, and pipeline / security content for the whole group. Over plain HTTP (or HTTPS with verification disabled) a network attacker between GitLab and the receiver reads all of it and can tamper with deliveries. Also set a Secret token and validate the X-Gitlab-Token header on the receiver. The GitHub-org analog is ORG-011; the per-project analog is SCM-026.

GLGRP-006: GitLab group CI/CD variable exposes a secret with a weak control

HIGH CICD-SEC-6 CWE-522

Reads GET /groups/{group}/variables and fires on a variable whose value matches a known credential shape (the shared find_secret_values catalog: PATs, cloud keys, provider tokens, PEM blocks) AND that is protected: false or masked: false. The value-shape gate is what keeps this low-FP: an ordinary unprotected config variable (a URL, a flag, a region) is not flagged, only an actual secret with a weakened control. The token body is never echoed, only its detector label. A fully protected and masked secret passes. This is the group-API surface the static .gitlab-ci.yml rules (GL-003 / GL-008) cannot see; needs a token with read_api and Owner / Maintainer access, and passes with a note when the endpoint is unavailable.

Recommended action

For each flagged group CI/CD variable (Group Settings -> CI/CD -> Variables), turn on both Protect variable and Mask variable (or Masked and hidden on newer GitLab). A group variable is inherited by every project in the group, so its blast radius is the whole group, not one project. Without Protected it is handed to pipelines on every branch and merge request, so a feature-branch push (or a fork MR where fork pipelines run) can print or use the credential; without Masked it appears in cleartext in any job log. Prefer scoping a real secret to the single project that needs it, or to a protected environment, and rotate any credential that has been exposed this way. The per-project / in-YAML analogs are GL-003 and GL-008.


Adding a new GitLab group governance check

  1. Create a new module at pipeline_check/core/checks/gitlab_group/rules/NNN_<name>.py exporting a top-level RULE = Rule(...) and a check(path, doc) -> Finding function. The orchestrator auto-discovers RULE and calls check with the parsed YAML document.
  2. Add a mapping for the new ID in pipeline_check/core/standards/data/owasp_cicd_top_10.py (and any other standard that applies).
  3. Drop unsafe/safe snippets at tests/fixtures/per_check/gitlab_group/-NNN.{unsafe,safe}.yml and add a CheckCase entry in tests/test_per_check_real_examples.py::CASES.
  4. Regenerate this doc:
python scripts/gen_provider_docs.py gitlab_group