Commit ceafd97e30

ceafd97e30ad65504e3fe2e784300f17b21d946c

parent: bd5cf5d7d1

Verified · cmc ci/build: success ci/test: failure

cmc <hello@cleberg.net> · 2026-09-29 00:25 UTC

lfs: refuse an upload token once its repository is archived

Ref #285

Layout: unified · split

CHANGELOG.org +2 −1
@@ -276,7 +276,8 @@ missing, =gitbayd admin backup --verify <archive>= names it, and
276276- Every LFS request also repeats the repository check for the token's
277277 key: a collaborator whose access is revoked or reduced, or a reader
278278 of a public repository made private, loses the token's use with the
279 access (#285).
279 access, and an upload token stops working once its repository is
280 archived (#285).
280281
281282* v1.36.0 — 2026-09-23
282283
internal/httpd/lfs.go +4 −1
@@ -75,8 +75,11 @@ func (s *Server) lfsAuth(r *http.Request, repo store.Repo) (string, int64) {
7575
7676// lfsKeyAllows repeats git-lfs-authenticate's access check for the key
7777// now: a deploy key by its binding, any other key by its account's
78// access narrowed by the key's scope.
78// access narrowed by the key's scope. An archived repo takes no uploads.
7979func (s *Server) lfsKeyAllows(keyID int64, repo store.Repo, write bool) bool {
80 if write && repo.Settings.Archived {
81 return false
82 }
8083 key, err := s.st.SSHKeyByID(keyID)
8184 if err != nil {
8285 return false
internal/httpd/lfsauth_test.go +30
@@ -222,3 +222,33 @@ func TestLFSDeployKeyToken(t *testing.T) {
222222 t.Errorf("deploy key of a disabled account: %q", op)
223223 }
224224}
225
226// An upload token minted before the repository was archived uploads
227// nothing after it, as git-lfs-authenticate would refuse to mint one.
228func TestLFSUploadTokenRefusedOnceArchived(t *testing.T) {
229 s, st, u := newTokenTestServer(t)
230 repo := lfsTestRepo(t, st, u.ID, "app", "private")
231 secret, err := s.lfsSecret()
232 if err != nil {
233 t.Fatal(err)
234 }
235 key := lfsTestKey(t, st, u.ID, "SHA256:owner", "full")
236 now := time.Now()
237 up := lfs.Sign(secret, repo.ID, key, "upload", now)
238 if op, _ := s.lfsAuth(lfsRequest(up), repo); op != "upload" {
239 t.Fatalf("upload before archiving: %q", op)
240 }
241 if _, err := st.UpdateRepoSettings(repo.ID, func(rs *store.RepoSettings) { rs.Archived = true }); err != nil {
242 t.Fatal(err)
243 }
244 repo, err = st.RepoByID(repo.ID)
245 if err != nil {
246 t.Fatal(err)
247 }
248 if op, _ := s.lfsAuth(lfsRequest(up), repo); op != "" {
249 t.Errorf("upload after archiving: %q", op)
250 }
251 if op, _ := s.lfsAuth(lfsRequest(lfs.Sign(secret, repo.ID, key, "download", now)), repo); op != "download" {
252 t.Errorf("download after archiving: %q", op)
253 }
254}