Join our Newsletter — 33% off our NHI Course
Home› Guides› Git Repository Security Guide: Signed Commits, Branch Protection…
Guide Software & AI Supply Chain

Git Repository Security Guide: Signed Commits, Branch Protection and History Rewrites

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 13 min read
On this page

Git lets anyone set any author name and email on a commit, and a forge's branch settings are the only thing standing between a stolen token and your release branch. Repository integrity therefore rests on three controls that work on any platform: cryptographically signed commits and tags that are actually verified, protected branches and tags that block direct pushes, force pushes and deletions, and a disciplined process for the rare occasions when history must be rewritten. This guide covers each in platform-neutral terms (OpenPGP, SSH and Sigstore gitsign signing, server-side Git settings, gittuf), with notes on how GitHub and GitLab implement them, plus detection, rollout and failure patterns. It is written for platform engineers, repository administrators and AppSec teams. For platform-specific hardening, see the GitHub and GitHub Actions Security Guide and the GitLab Security Guide.

Key takeaways

  • Author fields prove nothing. Git records whatever name and email the client is configured with; only a verified signature, or the forge's record of which account pushed, ties a commit to an identity.
  • Signing without verification is decoration. Enforce "require signed commits" on protected branches, keep an allowed signers list for SSH keys, and verify signatures in CI for release tags.
  • Protect branches and tags, including from admins. Forge rules apply to administrators only when you explicitly remove their bypass; release tags need protection as much as main.
  • Rewriting history does not remove a leaked secret. Clones, forks and cached views keep it. Revoke and rotate first; rewrite only when the data itself is sensitive.
  • Consider forge-independent policy. gittuf (an OpenSSF incubating project, in beta) records signed reference changes and write policies in the repository itself, so changes can be verified without trusting the forge.

Why commit identity is weak by default

A Git commit contains an author and a committer, each a free-text name and email set by local configuration. GitHub's documentation on vigilant mode makes the point directly: Git allows you to set the author and committer identity, which makes it difficult for others to be confident that commits were created by the person named. Two consequences follow:

  • Spoofed authorship is trivial: a commit can claim to be from a maintainer or from a bot such as dependabot[bot] without any key material. In the fake Dependabot commits campaign of July 2023, attackers used stolen personal access tokens to push commits made to look as if they came from Dependabot, adding a workflow that sent repository secrets to an attacker server.
  • Push identity lives on the server: the forge knows which account or token pushed, but that knowledge is not in the commit and does not travel with clones or mirrors.

Signatures move identity into the object itself. The forge's branch rules then decide which signed, reviewed changes may land.

Signing commits and tags: OpenPGP, SSH, X.509 and gitsign

Git supports three signature formats through gpg.format: openpgp (default), x509 and ssh. Set commit.gpgSign and tag.gpgSign to sign by default; tag.forceSignAnnotated signs annotated tags only.

MethodKey and trust modelVerificationStrengthsWeaknesses
OpenPGP (gpg.format=openpgp)Long-lived GPG key; web of trust; gpg.minTrustLevel sets the trust neededgit verify-commit, git verify-tag; forge shows Verified if key is uploadedMature, widely supported, works offlineKey management burden, expiry and revocation often neglected
SSH (gpg.format=ssh)Existing SSH key; gpg.ssh.allowedSignersFile maps principals to keys; gpg.ssh.revocationFile for revoked keysGit treats a key in the allowed signers file as fully trusted; otherwise undefined and verification failsNo new tooling; hardware-backed keys (FIDO) possibleNo trust levels; the allowed signers file must be distributed and maintained
X.509 / S/MIME (gpg.format=x509)Certificate from an enterprise CA, signing program gpgsm by defaultChain to trusted CAFits existing enterprise PKICertificate lifecycle and CA trust on every verifier
Sigstore gitsign (gpg.format=x509, gpg.x509.program=gitsign)Keyless: short-lived certificate from Fulcio bound to an OIDC identity; signature recorded in Rekorgitsign verify --certificate-identity=... --certificate-oidc-issuer=...No long-lived signing key; identity is your SSO or workload identitySigning needs network access; GitHub does not show gitsign signatures as Verified because the Sigstore root is not in its trust root and certificates are short-lived

Gitsign's token providers include interactive browser login, device flow for headless sessions, and workload identity sources such as github-actions and spiffe, which makes it suitable for signing bot commits with the bot's own identity. Its matchCommitter option verifies that the committer matches the certificate identity. For background on Fulcio and Rekor, see the Sigstore Guide.

Choosing: small teams on one forge get most value from SSH signing with keys registered on the forge and "require signed commits" enforced. Organisations with SSO and many repositories should evaluate gitsign for keyless signing tied to corporate identity, accepting that forge UI badges may not reflect it and that CI must run gitsign verify. Use OpenPGP or X.509 where existing PKI or offline signing is a requirement. In all cases, sign release tags: they are what consumers and build systems fetch.

Protected branches and tags

Server-side protection is what turns signing and review into enforcement. Plain Git servers offer a few knobs; forges offer rich rules.

ControlPlain Git server settingGitHubGitLab
Block history rewrites on pushreceive.denyNonFastForwards=trueForce pushes blocked by default on protected branches; "Block force pushes" ruleset ruleProtected branch "Allowed to force push" off
Block ref deletionreceive.denyDeletes=true"Allow deletions" off; "Restrict deletions" ruleProtected branch settings (review who can delete)
Reject malformed objectsreceive.fsckObjects=true (or transfer.fsckObjects)Managed by platformManaged by platform
Require signed commitsPre-receive hook running verification"Require signed commits" (branch protection or ruleset)"Reject unsigned commits" push rule (Premium or Ultimate)
Require reviewNot available"Require a pull request before merging" with approvals and code owner reviewMerge request approvals; code owner approval on protected branches
Restrict who can pushHook or file permissions"Restrict who can push to matching branches"; "Restrict updates""Allowed to push and merge"
Apply to adminsNot applicable"Do not allow bypassing the above settings"; ruleset bypass listRole-based; review who holds Maintainer and Owner
Protect tagsHookTag rulesets (restrict creation, update, deletion)Protected tags

GitHub's documentation notes that by default branch protection restrictions do not apply to people with admin permissions or custom roles with the "bypass branch protections" permission, and that an unsigned commit pushed to a branch requiring signatures must be rebased with a signature and force pushed. Rulesets add bypass lists for roles, teams and GitHub Apps, and push rulesets that apply across a repository's whole fork network. The OpenSSF Scorecard Branch-Protection check rates unprotected default and release branches as high risk, which makes it a useful external measure.

For tokens and apps that are allowed to push or bypass, see the GitHub Personal Access Tokens Security Guide and the GitLab Tokens Security Guide.

Forge-independent integrity with gittuf

Branch protection lives in the forge's database. If the forge, an administrator account or a bypass-capable app is compromised, policy can be weakened and history changed without anything in the repository showing it. gittuf addresses this by storing policy and an activity log in the repository itself:

  • Policy: stored at refs/gittuf/policy, with root of trust metadata (owner keys and a signature threshold) and rules on which actors may write to which branches or files, following The Update Framework model.
  • Reference State Log (RSL): stored at refs/gittuf/reference-state-log, a hash chain of signed entries recording each change to a ref, so reordering or dropping entries is detectable.
  • Threat model: its design document considers any party, including developers, bots and the forge itself, as potentially compromised, with threats such as weakening configured policies and tampering with the activity log.

gittuf describes itself as in beta and recommends using it in addition to existing forge security policies. It suits high-value repositories (release, infrastructure, signing configuration) where independent verification is worth the extra workflow.

History rewrites: when and how

Rewrites are sometimes necessary (removing personal data, large binaries or sensitive files) and always disruptive. GitHub's guidance on removing sensitive data lists the side effects: high risk of recontamination when a collaborator with an old clone pulls and pushes, broken diffs for closed pull requests, invalidated comments on open ones, and lost signatures because rewriting changes commit hashes and tools such as git-filter-repo remove the signatures. The rewritten commits remain reachable in forks, in other clones, through pull requests that reference them and via cached views by SHA.

  1. If it is a secret, revoke and rotate first. GitHub's guidance makes this the first step, and notes support will help remove data only where rotation cannot mitigate the risk. Rotation is the fix; the rewrite is cleanup. See the Git Secret Scanning Guide.
  2. Decide whether to rewrite at all: rotated credentials often need no rewrite. Personal data, licensed content or keys that cannot be rotated may.
  3. Freeze and coordinate: merge or close open pull requests, announce a freeze, and identify forks and clones.
  4. Rewrite with git-filter-repo: the Git project recommends it over git filter-branch; GitHub's procedure requires a version with --sensitive-data-removal (2.47 or later).
  5. Force push under controlled bypass: temporarily allow a named administrator to force push, then restore protection immediately and log the change.
  6. Clean up downstream: ask fork owners to delete or clean forks, have collaborators re-clone, and request removal of cached views from the forge where needed.
  7. Re-sign: re-sign rewritten release tags and record the old-to-new commit mapping for auditors.

Where identity fits: bots, deploy keys and service accounts

Much of a repository's write traffic is non-human: dependency bots, release automation, merge queues and CI jobs that push tags. Each is a privileged identity that can often bypass review.

  • Prefer app identities with narrow scope over personal access tokens held by humans or shared machine users.
  • Sign bot commits with the bot's own identity (gitsign workload identity, or a platform that signs bot commits), and treat unsigned commits claiming to be from a bot as suspicious.
  • Keep bypass lists short: list every app and role that can bypass rules, with an owner and justification, and review quarterly.
  • Deploy keys read-only by default: write-enabled deploy keys are push credentials without user attribution.
  • Block credentials in remotes: set transfer.credentialsInUrl to warn or die so tokens embedded in remote URLs are refused.

Rolling out repository integrity controls

  1. Baseline: run OpenSSF Scorecard or the forge's settings API across repositories; list default and release branches without protection, admins who can bypass, and apps with bypass rights.
  2. Protect default and release branches and tags: require pull requests, block force pushes and deletions, protect release tag patterns. Low risk, high value; do this first everywhere.
  3. Introduce signing in report mode: publish setup guidance (SSH signing or gitsign), enable vigilant mode or equivalent for volunteers, and measure the signed percentage per repository for a month.
  4. Enforce signing on critical repositories: turn on "require signed commits" for release, infrastructure and CI configuration repositories first; verify release tags in the release pipeline.
  5. Remove admin bypass: apply rules to administrators, with a documented break-glass role that alerts on use.
  6. Pilot gittuf on one or two high-value repositories alongside forge rules.

Rollback: if signing enforcement blocks legitimate work (for example a bot that cannot sign), add the bot to a time-limited bypass with an owner rather than disabling the rule. Keep rule changes in code (organisation rulesets or infrastructure-as-code) so reverting is a reviewed change.

Detection and measurement

  • Log and alert: branch protection or ruleset changes, bypass use, force pushes and ref deletions on protected refs, new deploy keys with write access, repository visibility changes, and mass deletions or renames. The GitHub Audit Log Detection Guide covers event names.
  • Hunt: commits whose author claims a bot identity but which were not pushed by that bot's app; unsigned commits on branches where the team normally signs.
  • Metric: percentage of default and release branches with protection that applies to admins.
  • Metric: percentage of commits to protected branches with verified signatures.
  • Metric: number of identities (users, apps, tokens) with bypass rights, and their trend.
  • Metric: force pushes to protected refs per month (target zero outside approved rewrites).

Framework mapping

FrameworkReferenceRepository control
NIST SP 800-53 Rev. 5CM-3, CM-5, SA-10, AC-6, AU-10Change control via reviewed merges; access restrictions for change; developer configuration management; least privilege on bypass; non-repudiation via signatures
NIST CSF 2.0PR.AA-05, PR.PS-06Least privilege and separation of duties for write access; secure development practices
NIST SP 800-218 SSDFPS.1Protect all forms of code from unauthorised access and tampering
OWASP CI/CD Top 10CICD-SEC-1 Insufficient Flow Control MechanismsBranch protection and required review prevent unreviewed code reaching release
MITRE ATT&CKT1195.002, T1078, T1485, T1552.001Supply chain compromise via pushed code; abuse of valid tokens; repository wiping; credentials committed in files

Common failure patterns

  • Stolen token, direct push: a personal access token with write access pushes straight to a branch with no protection, as in the fake Dependabot commits. Require pull requests and signed commits, and scope tokens narrowly.
  • Admins exempt by default: rules exist but administrators and bypass-capable apps are not covered, so a compromised admin account bypasses everything.
  • Destructive access through OAuth grants: in the Gitloker campaign, attackers wiped and renamed repositories using stolen credentials and phishing-authorised OAuth apps requesting repository delete rights. Restrict OAuth app approval and keep offline mirrors.
  • Signed but unverified: developers sign, but no branch requires it and no pipeline checks release tag signatures.
  • Rewrite instead of rotate: a leaked key is scrubbed from history but never revoked, while forks and old clones still hold it.
  • Unprotected tags: main is locked down, but anyone with write access can move a v1.2.3 tag that the build system trusts.

Practitioner checklist

  • Default and release branches require pull requests, block force pushes and deletions, and apply to administrators.
  • Release tag patterns are protected against creation, update and deletion by non-release identities.
  • Critical repositories require signed commits; release tags are signed and verified in the release pipeline.
  • Signing method chosen and documented (SSH with allowed signers, gitsign with identity checks, or PGP/X.509), with revocation handled.
  • Bot and CI identities use scoped app credentials, sign their commits, and appear on a reviewed bypass list.
  • Write-enabled deploy keys are inventoried and justified; transfer.credentialsInUrl blocks credentials in remote URLs.
  • Protection changes, bypass use, force pushes and deletions are logged and alerted.
  • A written rewrite procedure exists: rotate first, git-filter-repo, controlled force push, fork and clone clean-up, re-signing.
  • gittuf evaluated for the highest-value repositories.

Standards and references

Related NHI Mgmt Group resources: GitHub and GitHub Actions Security Guide · GitLab Security Guide · Sigstore Guide · OWASP CI/CD Top 10 CICD-SEC-1 Insufficient Flow Control Mechanisms Guide · Git Secret Scanning Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org