GitLab — Integration Setup
Prerequisites
- A GitLab group on GitLab.com or a self-managed instance
- Permission to create a group or personal access token
- CMDash Essentials tier or above
Read this before you start: merge-approval rules (
approvals_before_merge) are a GitLab Premium feature. On Free, CMDash will report your protected branches accurately but will report zero required approvals, because on Free there genuinely is no enforced approval requirement. That is a true reading of your controls, not a limitation of the integration — see How the numbers are computed.
Step 1: Create an access token
A group access token is preferred over a personal one: it survives the person who created it leaving, and it scopes naturally to the population you want to measure.
- Go to your group → Settings → Access tokens → Add new token.
- Name it
CMDash. - Set Role to Reporter. (Reporter can read protected-branch and approval settings; Guest cannot.)
- Select the
read_apiscope. Do not selectapi— that grants write access, and nothing here writes. - Set an expiry and copy the token.
Step 2: Connect in CMDash
- Navigate to Integrations in your CMDash dashboard.
- Find GitLab and click Connect.
- Enter:
- API Token — from Step 1, sent as the
PRIVATE-TOKENheader - Group ID or path — optional but recommended (
acme/platform, or the numeric ID). With a group, CMDash inventories that group and its subgroups. Without one, it inventories every project the token can see, which is an honest fallback but a fuzzier population. - Instance URL — self-managed only (
https://git.acme.internal). Leave blank for GitLab.com.
- API Token — from Step 1, sent as the
- Click Save & Test Connection.
What Syncs
| Data Type | GitLab API | Behaviour on failure |
|---|---|---|
| Project list | GET /api/v4/groups/{id}/projects or GET /api/v4/projects?membership=true | Fails the sync |
| Protected branches | GET /api/v4/projects/{id}/protected_branches | 403 fails the sync; 404 = no protection |
| Merge approvals | GET /api/v4/projects/{id}/approvals | 403/404 = GitLab Free, recorded as zero required approvals |
How the numbers are computed
- Coverage is over active projects. Archived projects are reported but excluded from every denominator — an archive cannot receive a change.
- Wildcard protected branches are matched properly. GitLab protected-branch names may be patterns (
*,release/*). A project whose default branchmainis protected by the pattern*is correctly read as protected; an exact-string compare would report a governed project as ungoverned. - A 403 on protected branches fails the sync. An unreadable rule is unknown, not absent, and reporting unknown as unprotected would fabricate a control failure.
- A 403/404 on approvals is treated differently, and deliberately so. It means merge approvals are not available on your plan — so there really is no enforced approval requirement, which is a genuine negative rather than an unreadable one. This is the opposite call to the protected-branches 403 above, because the two failures mean genuinely different things.
- Admin enforcement is never reported for GitLab. GitHub exposes
enforce_adminsdirectly. GitLab's nearest equivalents — push rules and "allowed to merge" access levels — are a different concept and also Premium-gated, so CMDash leaves the field absent rather than guessing. Absent means "this source does not report it", which is not the same claim as "administrators can bypass". Fleet admin-enforcement coverage will read as unmeasured, not 0%. minRequiredApprovalsis the weakest link, not the average — see the GitHub guide for why.
Controls this evidences
CMDash seeds a suggested SOC 2 CC8.1 mapping (unprotectedRepositoryCount == 0). It keys on a count of zero rather than a coverage percentage because the control asks whether a change can reach production unreviewed, and one ungoverned project is a yes.
Troubleshooting
- 401 Unauthorized: token expired or revoked. GitLab access tokens always have an expiry.
- 403 with "Refusing to report an unreadable branch as unprotected": the token's role is below Reporter, or it lacks
read_api. - All projects show zero required approvals: you are on GitLab Free. This is accurate — see the note at the top.
- Project count is lower than expected: without a Group ID, only projects the token is a member of are visible. Set the group.
- Self-managed instance unreachable: the instance URL must be reachable from CMDash and resolve to a public address. CMDash pins the connection to a validated address and never follows redirects, so a URL that redirects to an internal host will fail by design.
Rate Limits & Sync Behavior
| Setting | Value |
|---|---|
| Sync frequency | Every 4 hours by default (syncInterval is configurable per connection) |
| Pagination | per_page/page, 100 per page, capped at 300 projects per sync |
| Request volume | One call per project-list page, plus two per active project (protected branches + approvals) |
| Rate limit handling | Routed through CMDash's shared token-bucket limiter |
| Circuit breaker | Repeated failures open the breaker and the sync is skipped |
| Failed sync retry | Job queue retries 3 times with exponential backoff (starting at 30s) — see docs/developers/dead-letter.md |
Credential Rotation
To rotate:
- In GitLab: Settings → Access tokens → Add new token with the same role and scope.
- In CMDash: Settings → Integrations → GitLab → Edit, enter the new token, Save.
- Confirm a successful sync, then revoke the old token.
Known Limitations
- Merge approvals require Premium. See above. This is the single most important thing to understand about this integration's numbers on a Free plan.
- Admin/bypass enforcement is not reported. See above.
- Only the default branch is evaluated for protection. Patterns protecting other branches are read but only matched against the default branch.
- No merge history. CMDash reads the standing rules, not whether a specific merge request was approved. The rule state is what an auditor samples against.
- CI configuration is not read. Whether pipelines must pass before merge is expressed through project settings this integration does not inspect.
- The 300-project ceiling is a truncation, not a sample. Larger groups are cut off and the sync log carries a warning.