Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXVI · Git History RewritingOperations

Re-signing after rewrite — restoring the chain of trust

Advanced⏱ ~24 mingitgit-filter-repogpg

What you'll learn

  • Explain why every signature becomes invalid after a history rewrite (the commit bytes changed)
  • Run git filter-repo --re-sign with --gpg-key to re-sign every rewritten commit in one pass
  • Run git rebase --exec git commit --amend --no-edit -S as the manual re-sign procedure
  • Verify the re-signed history with git verify-commit and git log --show-signature

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A signature is a cryptographic assertion over the commit bytes. A history rewrite changes the commit bytes. The signature therefore no longer matches; every signature in the rewritten history is invalid until it is re-created.

Why signatures break

A signature is produced by signing the canonical commit bytes (author, committer, tree, parents, message) with a private key. The signature is stored alongside the commit in the object store. Verification reproduces the canonical bytes, recomputes the signature, and compares.

The history rewrite produces new commit bytes: the tree is new (paths or blob contents changed), the commit message may be new (text replacement), and the parent chain is new (every descendant was rewritten). The canonical bytes therefore differ; the existing signature does not match; the commit is reported as bad signature by git verify-commit.

flowchart LR
    A["original commit C1"] -->|"signed by key K"| B["signature S1"]
    B -->|"rewrite"| C["new commit C2"]
    C -->|"C2 != C1"| D["S1 does not verify"]
    D -->|"re-sign"| E["signature S2"]
    E -->|"verifies against C2"| F["good signature"]

The chain of trust is broken from the moment the rewrite runs. Re-signing restores it; without re-signing the repository’s signed history is unrecoverable.

Re-signing with filter-repo

The one-pass re-sign procedure:

git filter-repo --path .env --invert-paths --re-sign --gpg-key KEYID

The --re-sign flag tells filter-repo to re-sign every rewritten commit. The --gpg-key KEYID flag names the key to sign with (or --ssh-key for SSH signing). The flag combines the rewrite and the re-sign into one operation; no separate re-sign pass is needed.

The key must be available to the signing agent (gpg-agent or ssh-agent). The key must be trusted by the verifier (the public key in the team’s trust store). If the key is not in the agent, the operation fails with a signing error.

Re-signing with rebase

The manual re-sign procedure for cases where filter-repo’s re-sign is not available (an older filter-repo version, an edge case):

git rebase --exec 'git commit --amend --no-edit -S' --root

The --exec flag runs the command after every commit in the rebase. The --amend --no-edit -S re-signs the commit without changing its message. The --root flag starts the rebase from the root commit and rewrites every commit.

The procedure is slower than filter-repo’s --re-sign for large repositories because every commit is replayed sequentially. The procedure is the right fallback when filter-repo is unavailable.

Verification after re-sign

Three checks after the re-sign:

git verify-commit HEAD
git log --show-signature --oneline | head -20
git log --pretty=format:'%H %G?' | sort -u | head

The first verifies the HEAD commit. The second lists the last 20 commits with their signature status. The third summarises every commit’s signature status (G = good, B = bad, U = unknown, X = expired, Y = valid made by an expired key, R = revoked, E = signature can’t be checked, N = no signature).

Every commit should show G (good) or N (no signature). Any B (bad) or U (unknown) is a re-signing failure; the rebase or filter-repo pass must be re-run with the signing agent available.

Production discipline

  1. Re-sign as part of the rewrite. Combine the rewrite and the re-sign into one operation (filter-repo --re-sign --gpg-key) so the rewritten history is never in the “bad signature” state.
  2. Verify with three checks. git verify-commit HEAD, git log --show-signature, and the summary sort.
  3. Update the trust store. If the key changed (lost, compromised), the team’s trust store must be updated to recognise the new key.

Cross-course references

  • Linux for Production Sysadmins - Part XXXIV (ConfigMgmt) covers the key-management infrastructure that the re-sign procedure depends on.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers the repository layout where re-signed history lives.
  • Terraform for Production Sysadmins - Parts IX-XII (State) cover the state-file backend rewrites that may follow a history rewrite.

Quiz

Knowledge check · 4 questions

  1. Q1. What happens to GPG/SSH signatures after a history rewrite?

  2. Q2. git filter-repo --re-sign --gpg-key KEYID combines the rewrite and the re-sign into one operation.

  3. Q3. Name the manual re-sign procedure for cases where filter-repo --re-sign is not available.

  4. Q4. A team rewrites history with filter-repo but does not re-sign. Diagnose the failure.

    Time T0: team runs git filter-repo --path .env --invert-paths. Time T0+10m: team force-pushes with --force-with-lease. Time T0+1h: security team runs git log --show-signature and reports that every commit in the rewritten history shows 'bad signature'. The team's CI pipeline requires signed commits and fails every push.

Passing score: 75%. Answers are checked in this browser.