Pushes and the committer rule
A commit’s author and committer are text the person committing types into their git config: anyone can write anyone’s name and address there. What fremforge knows for certain is who pushed: the account whose HTTPS token, OAuth token or SSH key opened the connection. Two things build on that:
- Pusher attribution. Every push to a repository in your organisation is recorded with that account, whatever the rule below is set to.
- The committer rule. It can require that every commit new to the repository has a committer address that belongs to the person pushing it.
The record of who pushed
fremforge records each ref update in a push:
- the pusher: the account that authenticated;
- how they authenticated: HTTPS (a personal access token or the OAuth token from Git Credential Manager), an SSH key, a deploy key with its key id, or an Actions job token;
- the ref, and its old and new commit;
- in a repository whose committer rule is in warn or block mode, the commits that were new to the repository;
- the committer rule’s decision.
The record comes from Forgejo’s own authentication, never from anything the client sends. Commits that fremforge makes itself are not recorded: merges, the merge queue and edits in the web editor.
Find it in the console under Security › Push protection › Pushes and the committer rule. The page shows recent pushes and a lookup that finds the push which brought a given commit. With the API:
# Recent pushes, or those to one repository
curl -H "Authorization: Bearer $TOKEN" \
"https://frem.sh/_app/api/v1/orgs/acme/push-protection/push-records?repository=web"
# Which push brought this commit?
curl -H "Authorization: Bearer $TOKEN" \
"https://frem.sh/_app/api/v1/orgs/acme/push-protection/push-records?commit=<full commit id>"The lookup finds a commit among the first 500 new commits of each ref update, in repositories whose committer rule was in warn or block mode when it was pushed. With the rule off, a push is recorded with its refs and pusher but not its commits, so that recording never slows a push down. Every push is also in the audit log as git.push, with the deploy key id for a deploy-key push.
The committer rule
The rule is committer email must match pusher. Each commit that is new to the repository must have a committer address that is either:
- one of the pusher’s confirmed email addresses on frem.sh (Settings › Account). An address that was added but never confirmed does not count;
- or the pusher’s no-reply address,
<login>@noreply.frem.sh, which only that account can use.
| Mode | What happens |
|---|---|
| Off (the default for an organisation created before 2026-10-27) | Pushes are recorded; nothing is checked. |
| Warn (the default for an organisation created on or after 2026-10-27) | The push is accepted, and the pusher is told which commits do not match. |
| Block | The push is refused until every new commit matches. |
An organisation created on or after 2026-10-27 starts in Warn: no push is ever refused, and a pusher whose commit carries an address they have not confirmed is told so. An owner can turn it off, or set Block, at any time. Warn makes a push wait for the check: measured on 2026-10-27, about 90 ms in the median.
Only commits new to the repository are checked. A commit that is already on another branch was checked when it first arrived, so merging main into your branch does not make you answer for other people’s commits.
A refused push names the commits and addresses:
remote: fremforge: push refused: the committer of 1 new commit is not one of your verified email addresses.
remote: 3f2a9c1b8e7d someone@example.com
remote: Set user.email to one of them, rewrite these commits (git rebase -i, or git commit --amend --reset-author), and push again.What is not checked
- Pushes with an Actions job token. A workflow pushes as the job, not as a person.
- Platform bots. These are the accounts fremforge itself runs, such as the security-fix and Renovate bots.
- Exempt users you list on the rule, for example a service account that pushes as itself.
- Deploy keys. A deploy key has no person and no verified address. Its pushes are recorded with the key id, and not checked, unless you choose Refuse deploy-key pushes in block mode, which refuses them while the rule is in block mode.
- Commits fremforge makes: merges, the merge queue and web edits.
If the check cannot run
If fremforge cannot run the check (for example, the service that answers it does not respond within 2 seconds), the push is accepted in every mode. It is recorded as Not checked, and in block mode fremforge’s operators are alerted. Refusing every push whenever a check is unavailable would stop your teams’ work, so the rule fails open and the miss is visible: in the push log, and to the operators. Look for decision Not checked in the push records.
Setting it
In the console, Security › Push protection › Pushes and the committer rule sets the organisation’s rule. A repository can have its own rule, which replaces the organisation’s for that repository. Set it with the API:
# The organisation's rule
curl -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"mode": "block", "refuse_deploy_keys": false, "exempt_users": ["acme-release-bot"]}' \
"https://frem.sh/_app/api/v1/orgs/acme/push-protection/committer-rule"
# One repository's own rule, and back to the organisation's
curl -X PUT -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"mode": "warn"}' "https://frem.sh/_app/api/v1/orgs/acme/push-protection/committer-rule/repos/legacy-import"
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"https://frem.sh/_app/api/v1/orgs/acme/push-protection/committer-rule/repos/legacy-import"Changing a rule needs the policy:write scope, and every change is in the audit log as push-protection.committer-rule.updated. A rule you turn on or change applies to pushes within 60 seconds.
Before you turn on block
- Run warn for a while, and read the push log for Warned rows. They show who commits with an address they have not confirmed.
- Ask people to confirm the address their git config uses, or to set
user.emailto their no-reply address. - Importing history (pushing an existing project with other people’s commits) fails in block mode, because those commits are new to the repository and were committed by others. Import in warn mode, or exempt the importer for the import. More than 5000 new commits in one push are refused in block mode, because the rule checks at most 5000; push the history in parts.
Compared with GitHub
GitHub has the same rule, “Committer must match pusher”, in a limited preview for GitHub Enterprise (2026-10-12). It records the pusher per commit. fremforge records the pusher per ref update, with the new commits where the rule is on, and has a lookup by commit.