4.4 Repository Permissions & Git Tag Governance
Key Takeaways
- Azure Repos permissions resolve to Allow, Deny or Not set (inherited), and an explicit Deny overrides an Allow inherited from any other group.
- Force push is the permission that lets a user rewrite history and delete branches or tags; denying it on release branches is the core protection control.
- Annotated tags are full Git objects carrying a tagger, date and message and are the only tag type suitable for release marking; lightweight tags are just a pointer.
- Signed tags (git tag -s) add a GPG or SSH signature that proves provenance for compliance and supply-chain attestation.
- GitHub tag protection is expressed through rulesets targeting tag patterns such as v*, restricting updates and deletions.
4.4 Repository Permissions & Git Tag Governance
Quick Summary: Securing a production Git repository requires access governance and immutable release tagging. In Azure Repos, permissions like Force push and Bypass policies when completing pull requests must be locked down to prevent unauthorized history destruction, and an explicit Deny always wins over an inherited Allow. Annotated and signed tags mark release points, while GitHub rulesets restrict who may update or delete a protected tag pattern such as
v*. Recovery and secret scrubbing follow in Section 4.5.
Repository Permissions & Security Models
Access control in version control systems protects intellectual property, enforces branch policies, and satisfies regulatory compliance audits (e.g., SOC 2, ISO 27001). Exam AZ-400 tests the specific permission hierarchies of Azure Repos and how they map to GitHub repository roles.
Azure Repos Security Hierarchy & ACLs
Azure Repos enforces permissions through an Access Control List (ACL) inheritance tree:
Permissions can be set to Allow, Deny, or Not Set (inherited). In Azure DevOps, an explicit Deny always overrides an Allow.
Azure Repos Granular Security Model:
Repository Permissions:
├── Read: Clone, fetch, and browse code files
├── Contribute: Push commits to unprotected branches; create PRs
├── Create branch: Create new branches
├── Create tag: Create lightweight or annotated tags
├── Manage permissions: Administer repository security ACLs
└── [CRITICAL GOVERNANCE PERMISSIONS]
├── Force push (rewrite history, delete branches and tags)
├── Bypass policies when completing pull requests (Break-glass role)
└── Bypass policies when pushing (Direct push to protected branches)
Critical Azure Repos Permissions Evaluated on AZ-400
- Force Push (rewrite history, delete branches and tags):
- Operational Function: Allows a developer to execute
git push --force, overwriting remote commits with a rewritten local history, or delete remote branches. - DevOps Best Practice: Must be set to Deny for all contributors on production branches (
main,release/*). Restrict strictly to Project Administrators during emergency data-scrubbing operations.
- Operational Function: Allows a developer to execute
- Bypass Policies When Completing Pull Requests:
- Operational Function: Allows an authorized user to complete a pull request even if branch policies fail (e.g., build validation pipeline failed, required reviewers rejected, or comment threads remain active).
- DevOps Best Practice: This is the "break-glass" permission. Reserve exclusively for Release Managers or Site Reliability Engineers (SREs) for emergency production hotfixes.
- Bypass Policies When Pushing:
- Operational Function: Allows a user or service principal to push commits directly to a branch protected by policies, completely bypassing the pull request workflow.
- DevOps Best Practice: Used strictly for automated release services (such as semantic release bot service principals bumping versions).
Azure Repos vs. GitHub Repository Roles
| Operational Capability | Azure Repos Permission | GitHub Equivalent Role / Setting |
|---|---|---|
| View & Clone Repository | Read (Allow) | Read base role |
| Push to Topic Branches | Contribute (Allow) | Write role |
| Triage Issues & PRs | Contribute + Boards access | Triage role |
| Bypass Branch Policies | Bypass policies when completing PRs | Admin role or "Allow specified actors to bypass rulesets" |
| Modify Access & ACLs | Manage permissions | Admin role / Repository Owner |
| Prevent History Rewrites | Force push = Deny | Ruleset setting: "Block force pushes" |
Git Tag Management: Lightweight, Annotated & Cryptographic Signing
A tag represents an immutable reference pointing to a specific commit in the Git history graph, universally employed to designate release milestones (e.g., v1.0.0, v2.4.1-lts).
Git Tag Architectures: Lightweight vs. Annotated:
1. Lightweight Tag (Mutable Pointer): 2. Annotated Tag (Immutable Git Object):
refs/tags/v1.0.0 refs/tags/v1.0.0
│ │
▼ ▼
Commit Object (SHA: 4a1c7...) Tag Object (SHA: 9f2e3...)
├── Tagger: Jane Doe <jane@contoso.com>
├── Date: 2026-09-05 10:14:22 UTC
├── Message: "Certified production release"
├── GPG Signature: [Cryptographic Proof]
└── Target: Commit Object (SHA: 4a1c7...)
1. Lightweight Tags (git tag <tagname>)
- A lightweight tag is simply a named file stored under
.git/refs/tags/containing a raw 40-character commit SHA. - It contains no author metadata, no creation date, and no descriptive message.
- It is essentially an immutable branch pointer that never advances.
- Use Case: Temporary local bookmarks or internal testing markers.
2. Annotated Tags (git tag -a <tagname> -m "<message>")
- An annotated tag creates a first-class Git object stored in
.git/objects/alongside commits, trees, and blobs. - It stores the tagger's name, email, creation timestamp, and a full multi-line release message.
- When pushed to Azure Repos or GitHub, annotated tags integrate natively with Release pipelines and release artifact packaging.
- Use Case: All formal production, staging, and milestone releases.
# Creating an annotated release tag
git tag -a v2.4.0 -m "Release v2.4.0: Implement OAuth2 authentication and Azure Key Vault provider"
# Pushing the specific tag to origin
git push origin v2.4.0
# Pushing all local tags to origin
git push origin --tags
3. Cryptographically Signed Tags (git tag -s)
For high-security, regulated environments (such as financial or medical software), tags should be cryptographically signed using a GPG (GNU Privacy Guard) key:
# Create a GPG-signed annotated tag
git tag -s v2.4.0 -m "Release v2.4.0: Verified build for production deployment"
# Verify the authenticity and cryptographic signature of a tag
git tag -v v2.4.0
Signed tags guarantee non-repudiation: they prove that the release was authored by a trusted individual holding the private GPG key and that the Git commit tree has not been tampered with in transit.
Tag Protection and Permission Inheritance
Tags mark immutable release points, so both platforms let you stop them being rewritten.
- Azure Repos exposes tag control through repository security: the
Create tagpermission governs who may push a new tag, andForce push (rewrite history, delete branches and tags)governs who may move or delete one. DenyForce pushon the repository for everyone except a break-glass group and release tags become effectively immutable. - GitHub uses tag protection rules (now expressed as rulesets targeting tags). A ruleset with a
v*pattern and theRestrict updates/Restrict deletionsrules blocks the classicgit push --force origin v2.1.0release-tag hijack. Rulesets can additionally require signed commits on the tagged ref.
Permission inheritance is a recurring exam trap in Azure Repos. Permissions flow project → all repositories → a single repository → a branch, and each level shows an explicit Allow, Deny, or Not set (inherited) value:
| Level | Set here when |
|---|---|
| Project (Git repositories node) | The rule applies to every current and future repository |
| Individual repository | One repository has stricter reviewers or tag rules |
| Branch | Only main or release/* needs the restriction |
Turning inheritance off on a repository does not clear existing explicit entries; it stops future project-level changes from reaching that repository. The safer pattern for contractors is to leave inheritance on and add an explicit Deny on the contractor group, because an explicit Deny beats an inherited Allow at every level.
A DevOps lead needs to configure permissions in Azure Repos for a financial services project. General developers must be allowed to create feature branches and complete pull requests, but must be prevented from pushing directly to the protected main branch or rewriting history. Additionally, a dedicated Site Reliability Engineering (SRE) group must be permitted to complete emergency hotfix pull requests even if automated build validation pipelines have failed. Which permission configuration satisfies these requirements?