GitHub Security Advisory: GHSA-7rmh-48mx-2vwc
Package: github.com/sigstore/gitsign (GO)
Severity: MODERATE (CVSS 5.3)
CWE: CWE-295: Improper Certificate Validation, CWE-347: Improper Verification of Cryptographic Signature
Vulnerable versions: < 0.16.0
Patched in: 0.16.0

Summary: gitsign verify accepts signatures over go-git-normalized bytes, enabling trust confusion on malformed commits

Details:
## 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:** None

## Affected 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)

## Description

### Root cause: re-encoding instead of raw-byte verification

When `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:

```go
// internal/commands/verify/verify.go:63–92
c, err := repo.CommitObject(*h)          // go-git parses the raw object
...
c2 := new(plumbing.MemoryObject)
if err := c.EncodeWithoutSignature(c2); err != nil {  // re-encodes canonical form
    return err
}
r, _ := c2.Reader()
data, _ := io.ReadAll(r)

summary, err := v.Verify(ctx, data, sig, true)   // verifies re-encoded bytes, not raw bytes
```

The same pattern appears in `verify-tag`:

```go
// internal/commands/verify-tag/verify_tag.go:76–95
tagData := new(plumbing.MemoryObject)
if err := tagObj.EncodeWithoutSignature(tagData); err != nil {
    return err
}
```

### The loose-parsing assumption in go-git

The codebase itself acknowledges the problem in `ObjectHash`:

```go
// pkg/git/verify.