Use this page when setting up a new repo or group, after a migration, or for a periodic audit. In a regulated environment these controls are often audit evidence, so they must be on everywhere and provable.
The baseline for every repo
- Default branch is protected: no direct push, no force-push, no deletion.
- Merges need at least one approval from someone other than the author; two for infrastructure and production config.
- Approvals reset when new commits are pushed.
-
CODEOWNERSroutes reviews for sensitive paths (CI files, IaC, IAM). - Pipeline must pass before merge.
- Secret scanning and dependency scanning run on every MR.
- Release tags are protected.
- Repo has an owner team, a description and a README that says what it deploys and where.
Organise groups by ownership, not by history
<org>/
platform/ shared CI templates, IaC modules, tooling
infrastructure/ live Terraform per account or environment
services/<team>/ application repos, one subgroup per owning team
archive/ read-only, kept for history
- Permissions are granted to groups (synced from the identity provider), never to individuals.
- Maintainer and Owner roles are rare and reviewed quarterly.
CODEOWNERS example
# Everything needs a team reviewer
* @<org>/<team>
# CI and deploy config need platform review too
ci-config.yml @<org>/platform
/ci/ @<org>/platform
# IaC and IAM need two groups
/terraform/ @<org>/platform @<org>/security
Audit with the API (read-only)
# Protected branches for one project (swap the CLI for your git host)
git-host api "projects/<url-encoded-path>/protected_branches" | jq '.[] | {name, allow_force_push, code_owner_approval_required}'
# Approval rules
git-host api "projects/<url-encoded-path>/approval_rules" | jq '.[] | {name, approvals_required}'
# Every project in a group, with default branch and visibility
git-host api --paginate "groups/<group-id>/projects?include_subgroups=true&per_page=100" \
| jq -r '.[] | [.path_with_namespace, .default_branch, .visibility, .archived] | @tsv'
Loop the first two over the third to get a full report. Output a table of repos that miss any baseline item and open one MR or ticket per fix.
Apply settings as code
Use your git host's Terraform provider (or the platform's equivalent) for protected branches and approval rules, so changes are reviewed and drift is visible. See Terraform changes via merge request.
resource "git_branch_protection" "default" {
project = "<project-id>"
branch = "main"
push_access_level = "no one"
merge_access_level = "maintainer"
allow_force_push = false
code_owner_approval_required = true
}
Pitfalls
- Admins exempt from protection "for emergencies". Emergencies are when mistakes happen. Use a break-glass process with an audit trail instead.
- Personal access tokens with
apiscope used by CI. Use project or group tokens with the smallest scope and an expiry, or OIDC. - Archiving a repo that something still clones at build time. Search CI configs for the URL first.