From b3d1dca33882279a344c7cccc8f0795b35c29b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Fri, 14 Aug 2026 16:15:16 -0700 Subject: [PATCH 1/2] contrib: fail on verify-commits ancestry errors `verify-commits.py` must not authorize checkout when Git cannot inspect the requested commit or its ancestry. Reject ancestry command errors and validate the exact trusted root through Git before reporting success. Co-authored-by: Rob Hamilton <6456095+Rob1Ham@users.noreply.github.com> --- contrib/verify-commits/verify-commits.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/contrib/verify-commits/verify-commits.py b/contrib/verify-commits/verify-commits.py index b053fbd1ffe..f280e818d0e 100755 --- a/contrib/verify-commits/verify-commits.py +++ b/contrib/verify-commits/verify-commits.py @@ -14,6 +14,14 @@ import time GIT = os.getenv('GIT', 'git') +def is_ancestor(older, newer, root_name): + """Return whether older is an ancestor of newer, rejecting Git errors.""" + result = subprocess.run([GIT, "merge-base", "--is-ancestor", older, newer]) + if result.returncode not in (0, 1): + print(f'Failed to determine ancestry between "{older}" and "{newer}" for the {root_name} (git merge-base exited with {result.returncode}).', file=sys.stderr) + sys.exit(1) + return result.returncode == 0 + def tree_sha512sum(commit='HEAD'): """Calculate the Tree-sha512 for the commit. @@ -107,12 +115,13 @@ def main(): logging.debug("verify-commits: [in-progress] processing commit {}".format(current_commit[:8])) if current_commit == verified_root: + # Ensure the trusted root identifies an existing commit. + is_ancestor(verified_root, current_commit, "trusted Git root") print('There is a valid path from "{}" to {} where all commits are signed!'.format(initial_commit, verified_root)) sys.exit(0) else: # Make sure this commit isn't older than trusted roots - check_root_older_res = subprocess.run([GIT, "merge-base", "--is-ancestor", verified_root, current_commit]) - if check_root_older_res.returncode != 0: + if not is_ancestor(verified_root, current_commit, "trusted Git root"): print(f"\"{current_commit}\" predates the trusted root, stopping!") sys.exit(0) @@ -123,8 +132,7 @@ def main(): no_sha1 = False else: # Skip the tree check if we are older than the trusted root - check_root_older_res = subprocess.run([GIT, "merge-base", "--is-ancestor", verified_sha512_root, current_commit]) - if check_root_older_res.returncode != 0: + if not is_ancestor(verified_sha512_root, current_commit, "trusted Tree-SHA512 root"): print(f"\"{current_commit}\" predates the trusted SHA512 root, disabling tree verification.") verify_tree = False no_sha1 = False From 465bca734ebf22dc27dd8667224f1cf2b590fc9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Fri, 14 Aug 2026 16:15:44 -0700 Subject: [PATCH 2/2] contrib: reject divergent verify-commits history `verify-commits.py` must not authorize checkout for a commit whose history diverges from configured trust roots. Require proof that the commit is an ancestor of a root before skipping checks, and identify the failing root in errors. Co-authored-by: Rob Hamilton <6456095+Rob1Ham@users.noreply.github.com> --- contrib/verify-commits/verify-commits.py | 27 ++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/contrib/verify-commits/verify-commits.py b/contrib/verify-commits/verify-commits.py index f280e818d0e..12c0101952e 100755 --- a/contrib/verify-commits/verify-commits.py +++ b/contrib/verify-commits/verify-commits.py @@ -22,6 +22,15 @@ def is_ancestor(older, newer, root_name): sys.exit(1) return result.returncode == 0 +def predates(commit, root, root_name): + """Return whether commit is provably older than root, rejecting divergent history.""" + if is_ancestor(root, commit, root_name): + return False + elif is_ancestor(commit, root, root_name): + return True + print(f'"{commit}" diverges from the {root_name} "{root}", refusing to verify.', file=sys.stderr) + sys.exit(1) + def tree_sha512sum(commit='HEAD'): """Calculate the Tree-sha512 for the commit. @@ -119,23 +128,19 @@ def main(): is_ancestor(verified_root, current_commit, "trusted Git root") print('There is a valid path from "{}" to {} where all commits are signed!'.format(initial_commit, verified_root)) sys.exit(0) - else: - # Make sure this commit isn't older than trusted roots - if not is_ancestor(verified_root, current_commit, "trusted Git root"): - print(f"\"{current_commit}\" predates the trusted root, stopping!") - sys.exit(0) + elif predates(current_commit, verified_root, "trusted Git root"): + print(f"\"{current_commit}\" predates the trusted root, stopping!") + sys.exit(0) if verify_tree: if current_commit == verified_sha512_root: print("All Tree-SHA512s matched up to {}".format(verified_sha512_root), file=sys.stderr) verify_tree = False no_sha1 = False - else: - # Skip the tree check if we are older than the trusted root - if not is_ancestor(verified_sha512_root, current_commit, "trusted Tree-SHA512 root"): - print(f"\"{current_commit}\" predates the trusted SHA512 root, disabling tree verification.") - verify_tree = False - no_sha1 = False + elif predates(current_commit, verified_sha512_root, "trusted Tree-SHA512 root"): + print(f"\"{current_commit}\" predates the trusted SHA512 root, disabling tree verification.") + verify_tree = False + no_sha1 = False os.environ['BITCOIN_VERIFY_COMMITS_ALLOW_SHA1'] = "0" if no_sha1 else "1"