1. What It Does
A signature ties a commit to a key, and the key to a person who registered it on their account. A branch or tag with a signing rule only accepts commits and tags that carry one. The rules are:
- Signed commits on the default branch (a repository setting), on any branch a branch rule with Require signed commits matches (for example
release/*), and — in an organization — on the branches an organization ruleset names (or every branch). - Signed tags: every tag must be an annotated tag with a valid signature. A lightweight tag has no object to carry a signature, so it is refused.
Which commits are checked. The commits an update newly brings to the ref: those reachable from the new tip but not from the ref's previous tip, not from any other branch that is covered by a rule, and not from the default branch. So turning a rule on does not re-check history, a force-push is checked on the commits it rewrites (not on the ones it keeps), a branch cut from the default branch is free, and git push origin feature:main checks everything on feature that main lacks. Merge commits count like any other commit. A push that deletes a ref introduces nothing.
Limits: up to 1,000 commits and tags are verified per push or write, each at most 64 KiB; a bigger push is refused with the reason (push it in several smaller pushes, or use the override).
2. What Counts as Signed
A signature is accepted when all of these hold:
- It verifies against a key registered on a UnityFreak account: an OpenPGP key (RSA, primary or signing subkey) or an SSH key (ed25519, RSA or ECDSA). Deploy keys are repository credentials, not people, and are never used to verify a signature. Other OpenPGP algorithms (EdDSA, ECDSA) are not verifiable yet and are refused as unsupported, and so is an OpenPGP signature made with the old SHA-1 hash (SHA-2 only).
- The key's owner is someone who may vouch for the commit: the person pushing or merging; the person named in the commit's author, committer or (for a tag) tagger line — matched by a verified account e-mail or the account's noreply address, upper or lower case — who has at least write access to the repository; and, for the commits of a pull request, the pull request's author, whether or not they have write access. The name on the commit is only a way to find the key; a commit that claims to be from someone but is not signed with their key verifies nothing. The reverse is not checked: the signature does not bind the author line, so a pull request author can land a commit whose author line names a colleague but which only the author signed. What the badge and the audit log show is the signer's account, not the name on the commit.
- The key is not revoked and not expired at the time of the push. The date inside the commit is never trusted, so a commit signed years ago with a key that has since expired is refused when pushed today. Removing a key from your account revokes it at once. A revocation or an expiry stored in an OpenPGP key block you registered is honoured; to revoke a key you can always just remove it.
One more accepted signer: the platform itself, for the commits and tags it creates in this repository (section 5).
3. How to Sign
First register the public key in your account (Settings → GPG keys, or SSH keys). Then tell git to sign. With an OpenPGP key:
git config --global user.signingkey <your GPG key id>
git config --global commit.gpgsign true
git config --global tag.gpgsign true
Or with an SSH key:
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
To sign commits you already made (everything since <base>):
git rebase --exec 'git commit --amend --no-edit -S' <base>
Sign a tag with git tag -s v1.0 -m "Release 1.0".
4. What a Rejection Looks Like
remote: UnityFreak: push rejected - signing is required here and this push does not meet it (required commit and tag signing):
remote: UnityFreak: commit 1a2b3c4 is not signed
remote: UnityFreak: tag refs/tags/v1 is a lightweight tag - a signed tag must be annotated (git tag -s)
remote: UnityFreak: Sign your commits and tags, then push again ...
! [remote rejected] main -> main (pre-receive hook declined)
Every line says one of these (fixed wording, nothing from the commit):
unsigned — the commit or tag is not signedlightweight_tag — the commit or tag is a lightweight tag (a signed tag must be annotated: git tag -s)bad_signature — the commit or tag has a signature that does not verifyunknown_signer — the commit or tag is signed with a key that is not registered on a UnityFreak account (or was removed from it)signer_not_authorized — the commit or tag is signed by a key whose owner is neither you nor the commit's author or committer with write access hererevoked_key — the commit or tag is signed with a revoked keyexpired_key — the commit or tag is signed with an expired keymalformed_signature — the commit or tag has a signature that cannot be readunsupported_signature — the commit or tag has a signature in a format the platform cannot verify (OpenPGP: RSA keys with a SHA-2 hash; SSH: ed25519, rsa or ecdsa)too_large — the commit or tag is too large to verifynot_a_commit — the commit or tag is a branch that does not point at a commit
A push with several refs is refused as a whole: none of its refs moves, and the rejected objects are discarded. If the check cannot run, the push is refused and can be retried. Two narrow moments are the exceptions: for the minutes of a platform update the check can be skipped, and a push that was already being verified when an owner removed the rule is allowed.
6. The Audited Override
When a refusal is wrong for the moment (an emergency fix, a key being rotated), a person the repository allows can push past it once with a reason:
git push -o signing-bypass="emergency fix, signing key being rotated" origin main
- The reason must be 8 to 300 printable characters. It is recorded in the audit log (
signing_push_bypassed) with who pushed and which refs; a refusal is recorded too (signing_push_blocked). - It works over HTTPS and SSH and covers that one push. Who may use it is a repository setting: admins (default), anyone with write access, or nobody; an organization ruleset can disable it.
- A deploy key can never override, and neither can auto-merge or the merge queue.
- A refused web edit, merge, branch or tag creation shows what is wrong and, for a person who may override, a reason field (the request field is
signingBypassReason).
7. What Is and Isn't Covered
Not every way a commit can reach a repository can be checked the same way, and the guarantee is only as wide as this list. The repository's Settings page shows the same list.
- git push (HTTPS and SSH)coveredBefore any branch or tag moves, the pre-receive hook verifies the signature of every commit the push newly introduces to a covered branch, and requires every covered tag to be an annotated, signed tag. Over HTTPS, over SSH, for a new branch, a force-push, several refs in one push. A deleted ref introduces nothing and is allowed.
- Web editor and applied review suggestionscoveredOn a covered branch the platform creates the commit and signs it with its own signing key; when no platform signing key is configured the edit is refused. A commit made on a new branch is checked when that branch is covered.
- Pull request merge (merge, squash, rebase)coveredMerge: the pull request's own commits that the target branch does not have yet must already be signed, and the merge commit the platform creates is signed by the platform. Squash and rebase: the platform creates the commits (the squash commit, the replayed chain) and signs them with its own signing key; the pull request's original commits are not re-checked, because they are not what lands. Without a platform signing key a merge into a covered branch is refused.
- Auto-mergecoveredChecked when the pull request lands, like a manual merge. Auto-merge can never carry an override: it stays armed and lands by itself once the commits are signed (or an admin changes the rule).
- Merge queuecoveredChecked when the queued candidate is built and when it lands. A pull request with unsigned commits leaves the queue; the queue cannot override a refusal.
- Resolving pull request conflicts in the browsercoveredThe resolution commit is created and signed by the platform on a covered branch.
- Creating a branch in the web app or APIcoveredA new covered branch may only start at a commit whose history is already trusted: commits on the default branch or on another covered branch are, and any other commit that is not signed is refused (an override is possible).
- Creating a tag or release in the web app or APIcoveredWhen tags are covered the platform creates an ANNOTATED tag and signs it with its own signing key (the tag message defaults to the tag name); without a platform signing key the tag is refused.
- Creating the first README of an empty repositorynot coveredWrites a README commit generated by the platform into an empty repository, before any rule can have been set up.
- Import from URLnot coveredCopies the source history once, before the repository accepts pushes; years of upstream history are not signed by the platform's rules.
- Pull mirror syncnot coveredSync replicates the upstream repository exactly; pushes are refused while a pull mirror is enabled.
- Push mirror syncnot coveredWrites to the external mirror target, never to this repository.
- Forkingnot coveredCopies existing history into a new repository; nothing new is introduced.
- Generating a repository from a templatenot coveredWrites the first commit of a brand-new repository as the person generating it (the platform cannot sign for them), before any rule can have been set up for that repository.
- Sync forkcoveredSync fork only fast-forwards the fork's default branch onto commits that already exist upstream; the platform creates and signs nothing. When a rule covers that branch, the commits the fork does not have yet must carry valid signatures this repository accepts for the person who syncs (as for a push of them, or a merge of a fork pull request): upstream commits signed by other people are refused unless an override with a recorded reason is allowed. A platform signature made upstream does not count here.
- Dependency fix pull requests (the dependency bot)coveredThe platform signs the fix commit with the platform key when signing is required for the bot branch or for the branch the pull request merges into; when it is required and no platform key is configured the fix is refused (nothing is written) rather than leaving an unsigned commit that could not be merged. The bot can never use an override.
- Cross-repository pull request syncnot coveredStores a fork pull request head in an internal ref only. That content reaches a branch through a merge (checked: the contributor's own commits must carry the contributor's own valid signatures, a platform signature made in the fork does not count in this repository; a squash or rebase lands fresh commits the platform signs here) or a push (checked: a commit that is only in an internal ref counts as new).
- Wiki editsnot coveredThe wiki is a separate repository edited on the web; it is not covered.
- Commits that were already in the repositorynot coveredTurning a rule on does not re-check history: commits reachable from the previous tip of the branch, from another covered branch or from the default branch are never re-checked.
- Branches no rule covers, and refs outside branches and tagsnot coveredA rule covers the branches it names. Unsigned commits can live on an uncovered branch; they are checked when they reach a covered branch (by push, merge or branch creation). Notes, internal refs and other namespaces are not branches or tags.
- Signatures made by the platformcoveredCommits and tags the platform creates on your behalf are signed with the platform's own key, which the platform trusts explicitly for that purpose, and only in the repository they were created in (the signature names that repository: a commit the platform signed in another repository, or in a fork of this one, is not accepted here). A commit that is only platform-signed says "created by UnityFreak's merge machinery in this repository after the merge gates passed", not "typed by this person with their own key".
8. Settings & Organization Rules
Repository admins set Settings → Required commit signing: signed commits on the default branch, signed tags, and who can override. Other branches are covered by a branch rule with Require signed commits. Changes are recorded in the audit log with the old and new value (repo_signing_policy_changed); turning a rule off or opening the override is marked as a relaxation.
Organizations. An organization ruleset (Organization → Rulesets) can require signed commits on the branches its ref pattern names (empty = every branch), require signed tags, and disable the override, for every repository it targets. Like all rulesets, these only ever make a repository stricter; the strictest rule wins.
Existing repositories are never switched on for you. Nothing here re-checks existing history.