GHSA-7rmh-48mx-2vwc: gitsign verify accepts signatures over go-git-normalized bytes, enabling trust confusion on malformed commits
Summary`gitsign verify` and `gitsign verify-tag` re-encode commit/tag objects through go-git's `EncodeWithoutSignature` before checking the signature, instead of verifying against the raw git object bytes. For malformed objects with duplicate `tree` headers, git-core and go-git parse different trees: git-core uses the first, go-git uses the second. A signature crafted over the go-git-normalized form (second tree) passes `gitsign verify` while git-core resolves the commit to a completely different tree. This breaks the invariant that a verified signature, the commit semantics git-core presents to users, and the object hash logged in Rekor all refer to the same content.Severity**Medium** (CVSS 3.1: 5.7)`CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N`**Attack Vector:** Network — a malformed commit can be distributed via any accessible git remote**Attack Complexity:** High — exploitation requires crafting malformed objects that also bypass git server fsck checks (not universally enabled)**Privileges Required:** None — the most impactful form (signature replay) requires no signing key**User Interaction:** Required — a victim must run `gitsign verify` on the malformed commit**Scope:** Unchanged — impact is confined to the repository under verification**Confidentiality Impact:** None**Integrity Impact:** High — a verified signature appears to endorse content different from what git-core resolves and presents to users**Availability Impact:** NoneAffected Component`internal/commands/verify/verify.go` — `(o *options).Run` (line 75)`internal/commands/verify-tag/verify_tag.go` — `(o *options).Run` (line 77)`pkg/git/verify.go` — `ObjectHash` (lines 126–158, specifically the `commit()` round-trip at 161–176)CWE**CWE-347**: Improper Verification of Cryptographic Signature**CWE-295**: Improper Certificate Validation (secondary — the mismatch allows a cert to appear to cover content it never covered)DescriptionRoot cause: re-encoding instead of raw-byte verificationWhen `gitsign verify` is invoked, the commit is opened via go-git and its body is reconstructed through `EncodeWithoutSignature` before being passed to the cryptographic verifier:The same pattern appears in `verify-tag`:The loose-parsing assumption in go-gitThe codebase itself acknowledges the problem in `ObjectHash`:go-git's loose parsing means that for a commit containing two `tree` headers, it silently discards the first and retains the second. `EncodeWithoutSignature` then produces a canonical commit body containing only the second tree — which can differ from what git-core resolves.Divergent verification paths confirm the inconsistencyThe `git verify-commit` path (`internal/commands/root/verify.go`) receives the raw commit bytes directly from git-core and does **not** re-encode them:The two paths therefore reach opposite conclusions for the same malformed commit: `git verify-commit` fails (raw bytes with both trees ≠ signed canonical bytes), while `gitsign verify` succeeds (re-encoded bytes match signed bytes).Concrete attack: signature replay without a signing keyAn attacker does not need a signing key to trigger the confusion. Given any existing legitimately gitsign-signed commit from Alice:An attacker crafts a new malformed commit object:**`gitsign verify`**: go-git picks T1, re-encodes, Alice's signature verifies. Output: "Good signature from alice@corp.com."**`git log` / `git-core`**: uses T2 (attacker-controlled content).**Rekor lookup**: `ObjectHash` also goes through the go-git round-trip, so the logged hash is the T1-canonical hash — consistent with the forged verification output but not with the actual raw object.The attack requires only that the malformed object be accepted into the local repository (bypassing server-side fsck), and that the victim runs `gitsign verify`.Proof of Concept**Expected output:**Impact**Signature binding bypass**: `gitsign verify` reports a valid signature from a trusted identity for a commit that git-core resolves to completely different content (a different tree).**Signature replay without a key**: An attacker can reuse any existing gitsign-signed commit to produce a new commit that passes `gitsign verify` but points to attacker-controlled content, without possessing any signing key.**Rekor tlog inconsistency**: `ObjectHash` also goes through the go-git round-trip, so the hash stored in or looked up from the transparency log is the normalized hash, not the raw object hash. An auditor cross-referencing the tlog hash against the actual object store will see a mismatch.**Verification path divergence**: `git verify-commit` and `gitsign verify` reach opposite verdicts for the same malformed commit, undermining auditability.Recommended RemediationOption 1: Verify against raw bytes (preferred)Change the `gitsign verify` and `gitsign verify-tag` CLI commands to read the raw object bytes from the git object store and strip the signature header manually, mirroring what git-core does and what `commandVerify` already does when called by `git verify-commit`:This aligns the CLI verification path with the `commandVerify` (git verify-commit) path that already handles raw bytes correctly.Option 2: Detect and reject malformed objectsAdd a pre-verification check in `ObjectHash` and in the verification path that rejects objects with duplicate field headers (duplicate `tree`, `parent`, `author`, `committer`), returning an error rather than silently normalizing:This is a defense-in-depth measure but does not address the fundamental architectural issue of verifying re-encoded bytes.CreditThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).
CVSS v3.1: 5.3