Groups, Subgroups, Namespaces, Membership, Roles, Custom Roles, and Access Governance: Diagnostics, Failure Modes, Security, and Performance
Diagnose access that survives removal, higher inherited roles, over-broad group sharing, unavailable custom roles, and incomplete offboarding with an evidence-first, least-destructive workflow.
Learning objectives
- Use a repeatable evidence-first sequence to diagnose unexpected GitLab access without reflexively broadening roles or deleting resources.
- Explain why removing direct membership can leave inherited access intact and why a higher ancestor role dominates a lower child assignment.
- Detect group/project sharing as a hidden access path and distinguish tier/version absence from a permission error for custom roles.
- Run a safe intentionally broken membership example and repair only the actual access source.
- Build an offboarding checklist that inventories memberships, shares, service identities, credentials, identity-provider state, and audit evidence.
1. Diagnostic sequence: preserve → scope → enumerate → correct → verify
Unexpected access is dangerous precisely because the fastest “fixes” often make the graph harder to understand. Do not grant Owner/Maintainer to test whether permissions are the problem. Do not delete a group or transfer a project to shake loose stale access. Start with evidence.
| Step | Question | Evidence |
|---|---|---|
| 1. Preserve | What failed or remained accessible? | UI message, API status/body, timestamp, target path/ID, user ID; no secret values. |
| 2. Scope | Which host, top group, subgroup, project, and resource? |
glab auth status, group/project IDs and full
paths.
|
| 3. Enumerate | Which direct, inherited, shared, IdP, service/token paths exist? |
Members UI, /members,
/members/all, share settings, token/service
inventory.
|
| 4. Classify | Is this role hierarchy, tier/offering/version, SSO policy, protected-resource policy, or credential state? | Official docs + current settings + status code. |
| 5. Correct | What is the least destructive source-level change? | Change/remove the actual parent/share/direct membership or policy. |
| 6. Verify | Did effective access change, and did unrelated access stay intact? | Independent effective-membership/API/UI test. |
2. Failure mode: “I removed the project member, but they still have access”
First inspect the direct project membership endpoint. Then inspect
the effective endpoint. If the direct row is gone but
/members/all still returns the user, that is evidence
of another access path—not evidence that GitLab failed to delete the
row.
PROJECT_ID="789012"
USER_ID="246810"
set +e
glab api "projects/$PROJECT_ID/members/$USER_ID"
DIRECT_RC=$?
set -e
printf 'direct_lookup_exit=%s
' "$DIRECT_RC"
glab api "projects/$PROJECT_ID/members/all/$USER_ID" --jq '{id,username,access_level,expires_at}'
Next walk upward through namespace ancestry and inspect group shares. Change the source membership that actually grants access. Do not add another lower project role in an attempt to “override” inheritance.
3. Failure mode: a parent Maintainer silently beats a child Developer
GitLab keeps permissions from the highest applicable role. This protects users from accidentally losing capabilities when nested teams add lower child memberships, but it surprises operators who expect child configuration to be subtractive.
| Source | Assigned role | Effective child impact |
|---|---|---|
| Top group | Maintainer (40) | Maintainer capability reaches descendants. |
| Subgroup direct | Developer (30) | Does not reduce the inherited Maintainer. |
| Project direct | Reporter (20) | Still does not reduce the inherited Maintainer. |
| Correction | Lower/remove parent role if scope is wrong | Then re-evaluate needed narrower membership. |
5. Failure mode: “Custom roles are missing, so I need Owner”
If a Free or Premium namespace does not expose custom roles, the cause is entitlement, not insufficient Owner role. Current custom roles are Ultimate. Escalating a user cannot create a tier-gated feature. On Self-Managed, version and license matter too.
The repair is a design decision: choose the nearest safe built-in role, redesign the scope so that role is sufficient, or—if the organization already has Ultimate and a stable use case—go through the custom-role governance process. Never purchase or upgrade merely to complete this course lab.
6. Intentionally broken example: direct removal with inherited fallback
The following local fixture reproduces the logic without touching a real user. The broken offboarding function removes only direct project membership and incorrectly reports success.
cat > /tmp/ch04-offboarding.json <<'JSON'
{
"user": "alex-test",
"memberships": [
{"kind": "ancestor_group", "scope": "platform", "access_level": 30},
{"kind": "direct_project", "scope": "platform/service-a/api", "access_level": 20},
{"kind": "shared_group", "scope": "platform/service-a/api", "access_level": 10}
],
"credentials": ["project-bot-token-fixture"],
"service_accounts": ["release-bot-fixture"]
}
JSON
python - <<'PY'
import json
p=json.load(open('/tmp/ch04-offboarding.json'))
# BROKEN logic: remove only direct_project.
remaining=[m for m in p['memberships'] if m['kind'] != 'direct_project']
print('remaining_membership_paths=', remaining)
print('effective_access_level=', max(m['access_level'] for m in remaining))
print('residual_credentials=', p['credentials'])
print('residual_service_accounts=', p['service_accounts'])
assert remaining, 'This assertion intentionally shows access remains.'
PY
Interpretation: effective access remains Developer-level (30) from the ancestor, plus another shared path and non-human residuals. The fix is not “run delete again.” The fix is to inventory each source and revoke/remove according to ownership and operational dependency.
7. Authentication succeeds but SSO/authorization still fails
A user can authenticate to GitLab yet lack the required group authorization. On GitLab.com SAML groups, SSO enforcement, identity linking, SCIM state, group membership, and protected-resource policy are distinct controls. On Self-Managed, instance SAML/LDAP configuration can add different failure points.
Evidence should answer: Did the user authenticate? Is the correct SAML identity linked? Is the user a member of the required top-level group? Did SCIM deactivate or re-add the identity? Does group sync map the IdP group to the intended GitLab role? Is the resource requiring SSO authorization? Avoid “resetting the password” when the failure is a group authorization path.
8. Production offboarding is a residual-access investigation
Do not define offboarding as “Remove user from project.” Use a structured inventory:
| Access surface | Question | Typical evidence/action |
|---|---|---|
| Ancestor/direct memberships | Which groups/projects grant access? | Direct/effective Members API, UI Source column; remove at actual source. |
| Group/project shares | Is access inherited from another group invitation? | Groups tabs/share settings; remove share or source membership. |
| SSO/SCIM/LDAP | Is the enterprise identity disabled and synchronization healthy? | IdP + GitLab identity state; verify deprovision propagation. |
| Personal credentials | Any PATs/SSH keys tied to the departing user? | Chapter 02 credential inventory; revoke/disable according to policy. |
| Project/group/deploy tokens | Did the human own or know automation credentials? | Inventory token/service ownership; rotate/revoke when risk warrants. |
| Service accounts/bots | Does automation remain intentionally owned? | Reassign owner, scope, expiration, and audit contact. |
| Protected resources | Did role removal actually block sensitive actions? | Read-only permission tests; avoid destructive push/deploy tests. |
| Audit evidence | Can you prove what changed and when? | Audit events where available, API snapshots, ticket/runbook record. |
9. Scale, rate limits, and API completeness
Large groups can have many members and nested projects. Members APIs
are paginated. A script that checks only page 1 can falsely certify
offboarding. Use --paginate with
glab api or implement REST pagination explicitly. Cache
or parallelize only after correctness; stale authorization evidence
is worse than a slower complete inventory.
Do not dump full member records into public CI logs: names, email fields, SAML/SCIM identifiers, and security-sensitive metadata can be private. Select only the fields required for the audit and store evidence according to retention policy.
10. Repair pattern: remove one source, verify fallback, then continue
When multiple access paths are intentional, remove them one at a time and observe the effective role after each change. This makes causality visible and reduces the chance of deleting too much access. In an incident, you may need faster containment, but still preserve evidence and document the emergency path.
# Example evidence-only sequence; do not run DELETE until the target is verified.
glab api "projects/$PROJECT_ID/members/all/$USER_ID" --jq '{id,username,access_level,expires_at}'
# Inspect ancestor group direct membership and any share source here.
# Perform the narrow approved source-level change.
# Re-run effective membership lookup and compare the access level/status.
Knowledge check
A direct project lookup returns 404 but /members/all returns Developer. Is GitLab inconsistent?
No. The effective endpoint includes inherited/invited paths. Trace the namespace/share source.
Why is adding a Reporter project membership not a fix for an inherited Maintainer role?
Roles are not subtractive. The higher ancestor Maintainer remains effective until its source changes.
A Free user cannot find custom roles. Should you grant Owner to reveal the setting?
No. Custom roles are Ultimate. Diagnose entitlement/version before changing authorization.
What is the first correction when an offboarded user has a leaked PAT?
Revoke/rotate/disable the credential first, then investigate exposure and clean residual authorization/content.
Why must an access-inventory script paginate?
Otherwise it can silently miss members beyond the first page and falsely report complete offboarding.
Summary
Authorization failures are graph problems. Preserve evidence, scope the resource, enumerate direct/inherited/shared/non-human paths, distinguish entitlement from permission, correct the true source, and verify effective access independently. The right recovery is usually narrower than “grant Owner” and safer than deleting or transferring resources.
Official references
- GitLab Docs — Roles and permissions
- GitLab Docs — Subgroups
- GitLab Docs — Group members API
- GitLab Docs — Project members API
- GitLab Docs — Project members
- GitLab Docs — Sharing projects and groups
- GitLab Docs — Custom roles
- GitLab Docs — Group access and permissions
- GitLab Docs — GitLab.com SAML SSO
- GitLab Docs — GitLab.com SCIM
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.