fix: commit author/committer come from the global git identity, not the target repo; verify identity + signing before push #425

Closed
opened 2026-08-17 15:52:04 +00:00 by hexajon · 3 comments
Owner

Problem

madt_commit writes the author and committer from the global git identity, not
from the target repository's local config. So a commit can land with the wrong
author and committer for the repo it belongs to, even when that repo's local
user.name / user.email are set correctly.

Reading the config does not reveal the mismatch: git config user.email and
git var GIT_AUTHOR_IDENT in the clone can both show the correct, repo-local
identity, while the commit madt_commit actually produces carries the global
one. The only way to see it is to read the finished commit object's author and
committer, and nothing does that before the commit is pushed.

On a forge that expects a specific author identity (and signed commits), this
means a wrong-author, or unsigned, commit can reach the forge with nothing
stopping it.

Gaps

  1. Identity source. madt_commit should take author and committer from the
    TARGET repo's local git config (the repo the commit lands in), not the global
    identity.

  2. No pre-push verification. Before push / finish, read each commit on
    the branch and check that its author email and committer email match the
    repo's expected identity, and, where the forge requires signing, that the
    commit is signed and verifies. Refuse on a mismatch and name the commit. A
    config check is not enough: the config can be right while the commit is wrong,
    so the guard must read the commit object.

  3. Unsigned merge commit. finish on a signing-required forge should not
    leave an unsigned merge commit. The server-side API merge can produce one even
    when the branch commits are signed. Use a merge style that creates none
    (fast-forward or rebase), or sign the merge.

  • #424 - signing config does not travel with the credential (the signing half).
  • #390 / #391 - forge resolved from the launch context, not the target repo; the
    same root, applied here to the commit identity.
  • #422 - re-sign the unsigned commits on main.
  • #370 - pinning a commit to the correct public author identity (the same class,
    one case).
## Problem `madt_commit` writes the author and committer from the global git identity, not from the target repository's local config. So a commit can land with the wrong author and committer for the repo it belongs to, even when that repo's local `user.name` / `user.email` are set correctly. Reading the config does not reveal the mismatch: `git config user.email` and `git var GIT_AUTHOR_IDENT` in the clone can both show the correct, repo-local identity, while the commit `madt_commit` actually produces carries the global one. The only way to see it is to read the finished commit object's author and committer, and nothing does that before the commit is pushed. On a forge that expects a specific author identity (and signed commits), this means a wrong-author, or unsigned, commit can reach the forge with nothing stopping it. ## Gaps 1. **Identity source.** `madt_commit` should take author and committer from the TARGET repo's local git config (the repo the commit lands in), not the global identity. 2. **No pre-push verification.** Before `push` / `finish`, read each commit on the branch and check that its author email and committer email match the repo's expected identity, and, where the forge requires signing, that the commit is signed and verifies. Refuse on a mismatch and name the commit. A config check is not enough: the config can be right while the commit is wrong, so the guard must read the commit object. 3. **Unsigned merge commit.** `finish` on a signing-required forge should not leave an unsigned merge commit. The server-side API merge can produce one even when the branch commits are signed. Use a merge style that creates none (fast-forward or rebase), or sign the merge. ## Related - #424 - signing config does not travel with the credential (the signing half). - #390 / #391 - forge resolved from the launch context, not the target repo; the same root, applied here to the commit identity. - #422 - re-sign the unsigned commits on main. - #370 - pinning a commit to the correct public author identity (the same class, one case).
Author
Owner

This splits cleanly, and only half of it is blocked.

Item 1 (author and committer taken from the TARGET repo's local git config, not the global identity) is self-contained with no open question attached, so it can be built now. The same goes for a pre-push guard that reads each commit OBJECT's author and committer, because a config check is not enough: the config can be right while the commit is wrong.

Items 2 and 3 (pre-push SIGNING verification, and the unsigned merge commit that finish can leave) are coupled to the signing-identity design in #424. They should follow that decision rather than pre-empt it.

This splits cleanly, and only half of it is blocked. Item 1 (author and committer taken from the TARGET repo's local git config, not the global identity) is self-contained with no open question attached, so it can be built now. The same goes for a pre-push guard that reads each commit OBJECT's author and committer, because a config check is not enough: the config can be right while the commit is wrong. Items 2 and 3 (pre-push SIGNING verification, and the unsigned merge commit that `finish` can leave) are coupled to the signing-identity design in #424. They should follow that decision rather than pre-empt it.
hexajon self-assigned this 2026-08-17 17:45:48 +00:00
Author
Owner

Item 1 landed in PR #448. madt_commit and madt_finish's commit step now take author and committer from the target repo's LOCAL git config, and a pre-push guard reads each commit OBJECT and refuses, naming the sha, on an author/committer email that does not match the repo's local identity. On a first push with no remote base it checks every commit reachable from the pushed branch (an identity check, independent of forge signing).

Items 2 and 3 (signing verification, and the unsigned merge commit finish can leave) stay blocked on #424, so #448 references this issue rather than closing it.

Follow-up filed: #447 - the CLI git commit display author field still reads the Gitea account, so it can diverge from the actual local-identity author.

Item 1 landed in PR #448. `madt_commit` and `madt_finish`'s commit step now take author and committer from the target repo's LOCAL git config, and a pre-push guard reads each commit OBJECT and refuses, naming the sha, on an author/committer email that does not match the repo's local identity. On a first push with no remote base it checks every commit reachable from the pushed branch (an identity check, independent of forge signing). Items 2 and 3 (signing verification, and the unsigned merge commit `finish` can leave) stay blocked on #424, so #448 references this issue rather than closing it. Follow-up filed: #447 - the CLI `git commit` display `author` field still reads the Gitea account, so it can diverge from the actual local-identity author.
Author
Owner

Items 2-3 follow the #424 decision (no key custody; gate on the repo's actual signing requirement).

  • Item 2 (pre-push signing verification): gate it on the repo's ACTUAL require_signed_commits, the same fix #424 applies to the commit guard, and verify against the operator's wired key. No key material in madtea.
  • Item 3 (finish must not leave an unsigned merge commit): already prevented by the fast-forward-only default, which mints no merge commit (internal/opspec/paramdocs.go:584). If a signed merge is ever needed on a signing-required forge, it uses the operator's configured key from #424, not a madtea-held key.

Item 1 (identity from the target repo plus the pre-push identity guard) already landed in PR #448. Clearing needs-decision: this is workable now and flows from #424.

Items 2-3 follow the #424 decision (no key custody; gate on the repo's actual signing requirement). - Item 2 (pre-push signing verification): gate it on the repo's ACTUAL require_signed_commits, the same fix #424 applies to the commit guard, and verify against the operator's wired key. No key material in madtea. - Item 3 (finish must not leave an unsigned merge commit): already prevented by the fast-forward-only default, which mints no merge commit (`internal/opspec/paramdocs.go:584`). If a signed merge is ever needed on a signing-required forge, it uses the operator's configured key from #424, not a madtea-held key. Item 1 (identity from the target repo plus the pre-push identity guard) already landed in PR #448. Clearing needs-decision: this is workable now and flows from #424.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#425
No description provided.