"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Cas Hariri <cas.hariri@cinka.nl>
Subject:
gotd: branch namespace protection and forced push
To:
gameoftrees@openbsd.org
Date:
Mon, 26 Oct 2026 12:57:51 +0100

Download raw body.

Thread
  • Cas Hariri:

    gotd: branch namespace protection and forced push

Hi,

I just installed OpenBSD, beautiful system, love it.

I'm setting up gotd/gotsysd 0.129 so my git repositories behave like a
ledger: a push may add commits, but nobody, including someone with a
stolen key, can rewrite or delete history.

Setup

Every repository in gotsys.conf had:

     protect {
         branch namespace "refs/heads/"
         tag namespace "refs/tags/"
     }

What I tested

In a throwaway repository, from a git client:

   git push origin HEAD:refs/heads/ledger-test           # accepted
   git push --force origin HEAD~1:refs/heads/ledger-test # accepted
   git push origin --delete ledger-test                  # refused:
     refs/heads/: reference namespace is protected

The deletion was refused, but the forced push rewound the branch.
gotsys.conf(5) says that for a branch namespace, "attempts to change the
history of branches or delete them will be denied", so I expected the
second push to be refused as well.

How I worked around it

I added the branches by name to each protect block:

         branch "master"
         branch "main"
         branch "ledger-test"

With that, the same test gives:

   git push origin HEAD:refs/heads/ledger-test           # accepted
   git push --force origin HEAD~1:refs/heads/ledger-test # refused:
     refs/heads/ledger-test: reference is protected

Question

Is this expected behaviour, or a bug?

In case it helps: the rewind sends an empty pack, so I think it goes
through protect_refs_from_moving() in gotd/repo_write.c. There the
namespace check is

     if (strcmp(pe->path, refname) != 0)
         continue;

which never matches "refs/heads/" against "refs/heads/ledger-test".
The other path, protect_branch_namespace(), uses a prefix match. If it
is a bug, maybe this:

--- gotd/repo_write.c
+++ gotd/repo_write.c
@@ -1565,7 +1565,7 @@

          RB_FOREACH(pe, got_pathlist_head,
              &repo_write.protected_branch_namespaces) {
-            if (strcmp(pe->path, refname) != 0)
+            if (strncmp(pe->path, refname, strlen(pe->path)) != 0)
                  continue;

              err = protect_require_yca(&ref_update->new_id, 0,

Thanks,
Cas