v0.36.0
filex v0.36.0
Self-hosted file manager — Go single binary + multi-framework frontend.
Download a binary below, or pull a Docker image:
docker pull ghcr.io/brf-tech/filex:slim-v0.36.0
docker pull ghcr.io/brf-tech/filex:full-v0.36.0What changed
Upgrade notes
-
⚠⚠ Running a public demo (
FILEX_DEMO_MODE)? Upgrade before you link it
anywhere. The demo account is an administrator, and the guard covered
storage creation and plugins only. Measured by walking the real route table:
all 101 routes under/api/adminanswered, and not one returned 403 —
resetting the shared account's password, deleting users, repointing an
existing storage, making the server connect wherever a visitor pointed
smtp-test, applying an update./api/ai/admin/*is the same surface behind
a token and was reachable the same way, proven end to end. Writes are refused
now; reads are not, because showing the operator surfaces is what a demo is
for. -
⚠
GET /api/files/capabilitiesno longer returns service URLs to
anonymous callers. It still says whether OnlyOffice, drawio and the
converter are configured — that is what embedders probe it for — but not
where they live, because it was handing the operator's internal hostname to
anyone with curl. Signed-in responses are unchanged. If you read
onlyoffice_url,drawio_urlorconvert_urlfrom an unauthenticated
call, send credentials; the first-party consumers all already do. -
The container can drop root. Set
PUID/PGID(oruser:/
runAsUser) and filex runs as that user, chowning/dataonce. Nothing
changes without them: no variable means root, exactly as before, and the
upgrade path from a root-owned data directory was measured — the database
survives byte-identical andrm -rf ./datastops needingsudo.
Fixed
-
docs.filex.sh was built from the private tree, so it published an import
path that does not compile.docs/PLUGINS.mdtells a plugin author to
importgithub.com/brf-tech/filex/backend/pkg/pluginsdk; the published
module isgithub.com/brf-tech/filex/backend. Anyone following the plugin
guide copied a path that names a repository they cannot reach and does not
build.CONTRIBUTINGcarried two more of the same. The release step now
pushes the site's prose from the export, which is where every other
public artifact comes from.It also explains a second symptom: the site had been serving a v0.33.0 build,
so/REALTIMEwas a 404 while the README's first paragraph advertises
real-time collaboration and nine pages link to it,/CONFIGURATIONnever
mentioned ClamAV (nineteen times in the repo), and/PROTECTIONstill said
WebDAV and AI/MCP writes were "not yet" scanned — a security page describing
a gap that had been closed.
Added
-
PUID/PGID. The image installedsu-execand created afilex
account, then ran everything as root and used neither — so/datacame back
owned byroot:rootand deleting one's own data directory neededsudo.
There is now adocker/entrypoint.sh: setPUID/PGIDand it takes
ownership of the data directory once, records what it chowned to in a
.filex-uidmarker so later boots skip the walk, and drops privilege.⚠ The default is unchanged — still root. An existing install whose data
directory is full of root-owned files must keep working on upgrade, so
opting in is the operator's decision.docker run --user/ composeuser:
/ KubernetesrunAsUserare detected and left alone (nothing to drop, and
no permission to chown);PUIDalongside them says so in the log rather
than pretending. Measured on the published image: an old root-owned data
directory, thenPUID=1000, gives a 200 on/healthz, a byte-identical
installation.json, and arm -rf ./datathat no longer needssudo.⚠ Only the DATA directory is chowned. Storage roots — a bind mount, an NFS
or SMB share — are left as they are; they may be shared with other software
and re-owning them is not a container's decision.
Fixed
-
Every GitHub release page ended with two dead links, one of them
"Issues".scripts/export-public.shrewrote the GitLab host to
github.combut not the URL grammar: GitLab's/-/route separator
arrived verbatim, sohttps://github.com/…/filex/-/issues— the link a
reader clicks to report a bug — answered 404. Measured 2026-09-07:
104 of the 105 published releases carried it, not the recent few. The
export now translates the route shape (and renamesmerge_requests→
pulls,pipelines→actions), refuses to publish a tree where a
github.com/…/-/…survives, and the release footer points at
https://docs.filex.sh instead of raw markdown. All 105 published bodies
were corrected. -
Release notes said nothing. GoReleaser builds the body from
git log,
and the config filters dropdocs:,test:,chore:andci:— so the
published v0.34.2 page was, in full, a heading and one commit hash, while
CHANGELOG.mdcarried 1,445 characters of prose for that same version. The
release workflow now derives the body fromCHANGELOG.md(extending
scripts/release-notes.mjs, which already did this for the Umbrel store)
and hands it togoreleaser --release-notes. A version with no changelog
section fails the run before anything is published. ⚠ Capped at 20,000
characters, cut at a group or bullet boundary, with a link to the full
entry: measured on goreleaser v2.17.1, a body over 125,000 characters is
truncated silently, and what it cuts is the footer. -
The README's headline command left the reader in an empty file manager.
-v $(pwd)/data:/datamounts filex's own state directory — database, search
index, thumbnail cache — so files dropped into./datawere invisible and
the UI said "No storage configured". The command now seeds a local storage
from$PWDand keeps/datain a named volume, which is also what
docs/INSTALLATION.mdtold people to run.
Changed
- filex.sh links to the documentation site. 18 anchors, and
docs.filex.shappeared in none of them: the nav "Docs" and the footer
"Documentation" both pointed at raw GitHub markdown while a 38-page
VitePress site sat unlinked.
Security
-
⚠⚠ A public demo no longer hands every visitor a working admin panel.
FILEX_DEMO_MODEpublishes the credentials on purpose, which makes
"admin-only" mean "public" on that instance — and until now the only thing
refused was adding a storage. Measured on a local demo: all 101 admin
routes answered, none with a 403, including
POST /api/admin/users/{id}/reset-password,
DELETE /api/admin/users/{id},PATCH /api/admin/settings,
PATCH /api/admin/storages/{id}(repointing an existing storage — the
create-side guard never covered it),POST /api/admin/webhooksand
POST /api/admin/update/apply. One visitor resetting the shared password
locked out every other reader until the nightly restore.Every state-changing method under
/api/admin/…and/api/ai/admin/…is
now refused with 403 on a demo, as are changes to the shared account itself
(/api/auth/password,/api/auth/profile,/api/auth/totp/…). Reads are
untouched — a demo exists to show the operator surfaces — and so is every
ordinary install, where the middleware is a pass-through. Full list:
docs/DEMO.md.⚠
/api/ai/admin/…is the same admin surface behind an admin-scoped API
token, and a visitor could mint one atPOST /api/admin/ai-tokens(201) and
then drivePATCH /api/ai/admin/settings(200) with it. Guarding one mount
point without the other would only have moved the door. -
⚠⚠
/api/capabilitiesno longer tells anonymous callers where the
operator's services live. The endpoint is public by design (embedders probe
it before logging in) and it publishedexternal.<service>.urlplus the flat
onlyoffice_url/drawio_url/convert_urlto anybody who asked —
measured on demo.filex.sh:"url": "https://docs.example.com", with no
credential. This affected every install, not just demos. Anonymous
callers now getenabledandstateand no host; authenticated callers see
the payload unchanged, because the draw.io iframe and the convert modal need
a real address. OnlyOffice's document-server URL was never needed here — the
browser gets it from the authenticatedPOST /api/files/onlyoffice/config. -
On a demo, the audit log and the dashboard's
recent_activityno longer
print client IP addresses: with published credentials, one visitor's address
is readable by the next. Ordinary installs still show them. -
On a demo,
GET /api/admin/auth-providersno longer returns credentials in
clear. Itsconfig_redactedblock masks by leaf NAME, and the admin UI saves
a provider's whole config as one leaf calledconfig, so an OIDC
client_secretwent out in full under a field named "redacted".
Fixed
- The demo advertised two searches that returned nothing. The login splash
promised"invoice 2026" finds invoice_2026.pdfand the search box suggested
tag:report; on the demo corpus both answer 0 results — there is no file
with "invoice" in its name and no tags at all — whilemian.goand
package mainwork. The examples now name files the demo actually holds, and
a test pins them to the recorded corpus so a suggestion that stops being true
fails a build instead of greeting visitors.
Verify: sha256sum -c checksums.txt
- Documentation — https://docs.filex.sh
- Report a bug — https://github.com/BRF-Tech/filex/issues
- Full changelog — https://github.com/BRF-Tech/filex/blob/main/CHANGELOG.md
- Every release — https://github.com/BRF-Tech/filex/releases