Off unless you list something. LFSX_RESTRICTED=acme/assets,acme/game-* names the repositories whose objects take write access to read.

A forge grants read on a public repository to everyone who asks, and this server mirrors the forge, so by default the assets of a public project are as public as its code. That is usually what you want and occasionally the opposite of it: an engine you are happy to open with art you licensed and cannot redistribute, a plugin whose source is MIT and whose sample scenes are not.

Listing the namespace changes one thing. The question the forge answers stays the same, and the answer it gives is still the ceiling; what changes is the floor. pull is no longer enough, push is, so the people who could have committed the asset are the people who can fetch it.

LFSX_RESTRICTED=acme/assets

An entry is org/repo, or org/prefix-* for a run of repositories, or org/* for all of an organisation's. Matching ignores case, an entry naming no repository is dropped rather than widened to the whole organisation, and anything not listed is untouched.

Why write, rather than a list of people

Because the forge already keeps that list, and keeping a second one is how the two drift apart. On a public repository a stranger gets pull: true, push: false and a collaborator gets push: true, which is the line you were going to draw by hand. Taking it from the forge means no token this server issues, no file of usernames to edit, and no second place to revoke: remove somebody upstream and their access to the objects goes in the same breath, which is the property this server is built around.

The cost is that a read-only collaborator is refused, and there are projects where that is the wrong answer. If yours is one of them, say so on the issue tracker rather than working around it, because the fix is a real allowlist and it should be designed once.

What a client sees

git clone still works. The repository is public and that is the forge's business, not this server's.

git lfs pull fails on the objects, and the working tree keeps the pointer files. That is the intended outcome rather than a half-broken one: the caller has the repository, not the assets.

The floor covers the namespace, not only its objects. It is applied where permissions are resolved, which is one layer every route under /{org}/{repo} passes through, so a caller without write access is also refused git lfs locks, the object statistics and the repository's overview page. That is stricter than the feature strictly needs to be, and it is deliberate: one check in one place beats a list of per-route exceptions that drift apart.

A caller with no credentials is answered 401 with the challenge even when anonymous read is on, never 403. The distinction is the same one that page makes: 403 tells git-lfs the answer will not change, so it stops asking the credential helper and the person who does hold write access never gets to present it. The forge is not asked at all, since no anonymous caller can hold write access and there is nothing to learn.

A caller presenting a token the forge admits but who cannot push is answered 403, which is accurate: that answer will not change until somebody upstream changes it.

What it does not do

The pointer files stay public. They are committed to the repository, so the oid, the size and the path of every asset are readable by anyone who can clone, listed or not. This hides the bytes, not their existence.

It is not retroactive. Anyone who pulled before you listed the namespace still has what they pulled. Restricting a repository is a change to who can fetch from here from now on, not a recall.

Uploads were already restricted. Writing has always needed write, so listing a namespace changes nothing about pushing.

If you need an asset to be unreadable even to somebody who can reach the endpoint, this is the wrong tool and so is encryption at rest, which protects the disk rather than the caller. What you want is encrypting the file before it becomes an object, so the repository carries ciphertext and the oid is its digest. It works against a fully public endpoint, and it costs deduplication and compression, which are most of the reason to run this server.