vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Drop the refs/vole rule, which has nothing left to guard
592710c296c678e21bc1dc89341ad71606316500 · Keenan Nemetz · 17d ago · parent ec5c519c
Drop the refs/vole rule, which has nothing left to guard The namespace held the ACL and needed a rule of its own: a client who could write refs/vole/acl could have granted itself admin. The rules live in data/users.db now, so the namespace is empty and the special case only made refusals inconsistent — an owner pushing refs/vole/x got "server-owned", which is no longer true. Only branches and tags are writable, and that rule already refuses everything else, refs/vole/* included. The test now pins that: refs/notes/commits and refs/vole/acl are refused with the same message.
internal/hook/hook_test.go +11−21
| @@ -142,21 +142,6 @@ func TestAnonymousCannotPush(t *testing.T) { | |||
| 142 | 142 | } | |
| 143 | 143 | } | |
| 144 | 144 | ||
| 145 | // TestMetadataRefsAreServerOwned is the one that matters most: refs/vole/* holds | ||
| 146 | // the ACL itself, so a client that can write it can grant itself admin. | ||
| 147 | func TestMetadataRefsAreServerOwned(t *testing.T) { | ||
| 148 | f := newFixture(t, "hello\n") | ||
| 149 | for _, ref := range []string{"refs/vole/acl", "refs/vole/issues/000"} { | ||
| 150 | code, out := f.run("owner", ref, 0) | ||
| 151 | if code == 0 { | ||
| 152 | t.Fatalf("owner was allowed to push %s", ref) | ||
| 153 | } | ||
| 154 | if !strings.Contains(out, "server-owned") { | ||
| 155 | t.Fatalf("message for %s should explain ownership, got: %s", ref, out) | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | 145 | func TestProtectedBranch(t *testing.T) { | |
| 161 | 146 | f := newFixture(t, "hello\n") | |
| 162 | 147 | f.setACL(func(a *acl.ACL) { a.Protected = []string{"refs/heads/main"} }) | |
| @@ -190,11 +175,16 @@ func TestOversizedBlobRejected(t *testing.T) { | |||
| 190 | 175 | ||
| 191 | 176 | func TestArbitraryRefsRejected(t *testing.T) { | |
| 192 | 177 | f := newFixture(t, "hello\n") | |
| 193 | code, out := f.run("owner", "refs/notes/commits", 0) | ||
| 194 | if code == 0 { | ||
| 195 | t.Fatal("push to an unrelated ref namespace was allowed") | ||
| 196 | } | ||
| 197 | if !strings.Contains(out, "only refs/heads") { | ||
| 198 | t.Fatalf("unclear message: %s", out) | ||
| 178 | // refs/vole is no longer server state (access rules moved into the database), but it | ||
| 179 | // is still not a branch or a tag, so it is refused by the same rule as any other | ||
| 180 | // namespace rather than by a rule of its own. | ||
| 181 | for _, ref := range []string{"refs/notes/commits", "refs/vole/acl", "refs/vole/anything"} { | ||
| 182 | code, out := f.run("owner", ref, 0) | ||
| 183 | if code == 0 { | ||
| 184 | t.Fatalf("push to %s was allowed", ref) | ||
| 185 | } | ||
| 186 | if !strings.Contains(out, "only refs/heads/*") { | ||
| 187 | t.Fatalf("%s: unclear message: %s", ref, out) | ||
| 188 | } | ||
| 199 | 189 | } | |
| 200 | 190 | } | |