Skip to main content
CMDashCybersecurity Maturity Dashboard
← Back to GitLab

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.

  1. Go to your group → Settings → Access tokens → Add new token.
  2. Name it CMDash.
  3. Set Role to Reporter. (Reporter can read protected-branch and approval settings; Guest cannot.)
  4. Select the read_api scope. Do not select api — that grants write access, and nothing here writes.
  5. Set an expiry and copy the token.

Step 2: Connect in CMDash

  1. Navigate to Integrations in your CMDash dashboard.
  2. Find GitLab and click Connect.
  3. Enter:
    • API Token — from Step 1, sent as the PRIVATE-TOKEN header
    • 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.
  4. Click Save & Test Connection.

What Syncs

Data TypeGitLab APIBehaviour on failure
Project listGET /api/v4/groups/{id}/projects or GET /api/v4/projects?membership=trueFails the sync
Protected branchesGET /api/v4/projects/{id}/protected_branches403 fails the sync; 404 = no protection
Merge approvalsGET /api/v4/projects/{id}/approvals403/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 branch main is 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_admins directly. 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%.
  • minRequiredApprovals is 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

SettingValue
Sync frequencyEvery 4 hours by default (syncInterval is configurable per connection)
Paginationper_page/page, 100 per page, capped at 300 projects per sync
Request volumeOne call per project-list page, plus two per active project (protected branches + approvals)
Rate limit handlingRouted through CMDash's shared token-bucket limiter
Circuit breakerRepeated failures open the breaker and the sync is skipped
Failed sync retryJob queue retries 3 times with exponential backoff (starting at 30s) — see docs/developers/dead-letter.md

Credential Rotation

To rotate:

  1. In GitLab: Settings → Access tokens → Add new token with the same role and scope.
  2. In CMDash: Settings → Integrations → GitLab → Edit, enter the new token, Save.
  3. 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.

Ready to see your posture?

Start a 30-day free trial of any paid plan — a card is required, and it converts unless you cancel — or sign up for Free with no card at all.