Repository navigation
v0.43.0
filex v0.43.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.43.0
docker pull ghcr.io/brf-tech/filex:full-v0.43.0Warning
The v0.43.0 container images were not published — the image build failed on this tag. If you run filex in Docker, use v0.43.2, which carries the same changes with working images (v0.43.1 was tagged but never published). The binaries, desktop packages and npm packages on this page are complete.
What changed
The release that makes filex extensible. Apps are a second kind of
plugin — a sandboxed WebAssembly module that adds things to do with
files — and two ship alongside it as public repositories: e-Signature
and Convert.
A language pack is an app with nothing that runs, so filex can be
translated without waiting for a release; the text the server writes — mail,
notifications, the pages behind a link — comes from the same catalogue; and
the interface lays itself out right to left for the languages that read
that way.
Alongside them: desktop folder sync is now live in both directions, the
explorer's table is the only table left in the product, an operator composes
the instance's theme, tags are personal or shared with the team, and
every door that issues a credential refuses to issue one wider than the
caller.
⚠⚠ Security — upgrade promptly. Every filex up to v0.42.2 with ONLYOFFICE
configured accepts an unsigned save callback, which lets anybody who can
reach the server overwrite a file (Security). filex has never
enabled the editor without a JWT secret — an install with no secret has
editing off and is unaffected — so the one thing to check is the other
side: a Document Server running withJWT_ENABLEDoff sends unsigned
callbacks, and after this upgrade its saves fail. Turn JWT on there, with
the same secret filex holds.⚠⚠ A narrow API token could mint a wide one. Every door that issues
a credential — the desktop pairing endpoint, self-service API tokens, S3
access keys, NFS exports and SSH keys — measured the account behind the
caller instead of the caller itself, so a token restricted to reading, or
confined to one folder, could create a full-rights credential for the same
account and then use it (Security). Every one of them now
refuses with403 token_ceiling. The desktop door was found and fixed by
Berk Başarır (#35).⚠⚠ Desktop sync could replace real files — upgrade the server AND the
desktop app. A field report, traced and fixed by Berk Başarır
(#35), found filex's own sync client writing a server's
202 "preparing"status report to disk as the file and uploading it over
the original, conflict copies nesting by the thousand, a stale mirror
re-uploaded over a cleaned-up server, and a rename onto a taken name
destroying the file that had it (Fixed). The server fix protects
every client already installed; the new desktop build carries the rest.⚠⚠ A cache in front of filex could hand one person's answers to
everybody. API answers carried noCache-Control, so a CDN rule that
caches everything keptGET /api/auth/mefor two hours and served one
administrator's identity to every visitor. Every/apianswer is now
no-storeunless it is one of the four answers that say who the instance
is (Security). Found and fixed by Berk Başarır
(#41).⚠⚠ The notification bell named files its reader could not open. Queued
copies, moves and deletes were announced to every account, a member of an
RBAC storage read the names of files in folders they have no grant on, one
person's "mark all read" read everybody's alerts, and a tenant admin could
read every tenant's notification history (Security). Found and
fixed by Berk Başarır (#42, #43).⚠ Before upgrading, read Upgrade notes: API/MCP tokens
created with no scopes are rewritten to an explicit list that includes
admin— review and narrow them; identity providers saved on the admin
page come back switched off; existing tags become team tags, and a client
that sends no kind now makes personal ones; custom CSS is off until
switched on (see Changed); live desktop sync needs the new desktop build;
a desktop pairing needs a signed-in browser, and existing pairings keep
their old token until paired again; a rename onto a taken name answers
409; a program that wants the202"preparing" answer must now ask for
it (X-Filex-Accept-Prepare: 1); with SSO, sign-out now ends the identity
provider's session too, and the provider must allow filex's sign-in pages
as a post-logout return address;POST /api/admin/trash/emptyanswers
202while a large trash is still being emptied, and400for a value it
cannot read.
Added
-
Apps (app plugins) — a sandboxed plugin that adds things to do with
files. A WebAssembly module that adds actions to the file menu, screens
filex draws for it, and public pages for outside participants. Install from a GitHub repository
URL, a file upload or a URL, always through a permission review; the grant
is exactly the manifest's list and an upgrade that asks for more stops at
the review again. Actions run as ops-queue jobs (progress, cancel, open the
output), write through the same path as every other write, and are gated
by ACL, read-only storages and encrypted folders at submit. Host functions
under one permission each: files, per-file state, settings (secrets sealed),
the server's engines (ffmpeg, ImageMagick, LibreOffice, Ghostscript, poppler,
rsvg) with bare-token arguments, directory lookup, notifications (new
plugin.noticeevent), mail (60/hour), guarded outbound HTTP, public links
for outside participants (PIN with lock-out, exposed copies, jobs as the
link's creator) and host-held signing (per-tenant CA, certificates,
host_sign, admin CA download/rotate). Admin: Plugins → Apps tab with
the install wizard, settings, per-action overrides and logs. Explorer:
menu rows, ops-tray jobs, surface screens (form, steps, list, progress,
people picker, PIN, file chooser, preview, PDF fields, signature pad),
details-panel sections and an Apps side-bar section. Guest SDK
pkg/pluginkit(stock Go,GOOS=wasip1). Docs:APP-PLUGINS.md,
PLUGIN-KIT.md. Off in demo mode (FILEX_APP_PLUGINS_DISABLED). -
Language packs — filex can be translated without a release. An app can
add a language to filex, and it now does so end to end. A manifest that only carriesui_localesis a language pack:
it installs from the manifest alone (upload, GitHub repository or URL — no
module, no Go, no release), never starts a runtime, and is listed in
Plugins → Apps as a Language pack with each language's coverage of the
running version's catalogue (Español — 97% translated · the rest shows in
English). Its language joins every picker — the settings dialog, the admin
header, public share pages — and translates the explorer, the admin panel
and the public pages alike; anything it lacks shows in English. Integrity
moves to the manifest (the sha256 pin and, withFILEX_PLUGIN_TRUSTED_KEYS,
the signature are over the manifest). A right-to-left language lays the
interface out right to left (see Right-to-left layout). Format, grammar and limits:
PLUGIN-KIT.md→ Writing a language pack; a template repository,
BRF-Tech/filex-lang-template, walks a translator from export to install. -
Right-to-left layout. Arabic, Hebrew, Persian, Urdu and every other
right-to-left language a language pack adds now lays the whole interface
out right to left — the explorer, the admin panel, the public share, PIN
and file-request pages, the signing wizard and the embeddable components.
Whether a language is right to left is the server's one list
(wire.IsRTL, thertlflag on the offered-language rows); a page'sdir
is derived from itslang, and an embedded explorer takes its direction
from its OWNlocale, not the host page's (menus drawn under<body>
carry it too). The stylesheets are written in logical properties and the
admin panel in Tailwind's logical utilities (ms-,pe-,start-,
text-end, …), so English and Turkish draw the same pixels as before.
Gestures turn with the layout: a column's edge grows the way the pointer
drags it, a dragged column lands on the side of its neighbour the pointer
is on, the arrow keys move things the way they point, menus open toward the
line's end and flip at the screen's edge, and the frozen Name and Actions
columns draw their edges while a right-to-left table scrolls. Icons that
mean a direction (back/forward, breadcrumb and disclosure chevrons, undo,
sign-out, send, the panel icons) are mirrored; "open in a new tab",
refresh, media controls and document space — a PDF page's signature
boxes, images — never are. Commands, paths and URLs stay left to right;
file names, paths and people are isolated (<bdi>), and number pairs,
tag:…tokens and names inside a right-to-left sentence are isolated so
they keep their order. List fields (recipients, extensions, tags,
identities) accept the Arabic،, ideographic、and fullwidth,
commas. A source check (web/tests/quality/rtlLogical.test.ts) keeps
physicalleft/rightfrom coming back. Docs:docs/RTL.md. -
Appearance: the instance can wear your colours. A new admin screen
(Appearance,/admin/appearance) where an operator composes
named themes — twelve colours per light/dark variant, a corner radius and a
font stack — and picks one as the instance default. Ten more tokens are
derived server-side and stored with the theme, so the browser does no colour
maths at paint time, and the text colour on a coloured button is chosen by
WCAG contrast rather than assumed to be white. Served to the login page and
to anonymous share visitors as well, because a palette that stops at the
sign-in screen is not branding. Themes export and import as one JSON
document — the exported file is the upload body (migration 00051). -
Desktop sync is live: an edit on either side arrives in about a second.
A save in the web app's text editor, an ONLYOFFICE save, an upload or a
delete now reaches the synced folder on the desktop as it happens, and a
save on the desktop reaches the server just as fast — instead of waiting for
the engine's next 30-second lap. Measured on one Windows machine against a
local server, six edits each at random moments: browser text save → file on
disk 6.2–24.8 s (median 15.2 s) before, 0.19–0.22 s (median 0.20 s) now;
ONLYOFFICE callback → disk 0.5–25.9 s before, 0.19–0.21 s now; local save →
server 9.4–29.0 s (median 25.3 s) before, 0.43–0.61 s (median 0.46 s) now.
filex sync run --watchsubscribes to the server's change stream (the same
WebSocket the web explorer uses, authenticated with the engine's own token
through a ws-ticket) and watches the local folders with file-system events;
a change reconciles just the folder it happened in (one listing, not a walk
of the tree), and the--watchinterval is the safety net — it no longer
walks every pair, see Changed.--live=falseleaves only the interval. The
engine printslive: connected|polling|offline — …and the desktop app
shows it as one word under each synced folder (Live / Polling /
Offline). A newly paired folder starts syncing at once instead of on the
next lap. See Folder sync. -
Realtime: recursive, presence-less
watchsubscriptions.
{"type":"watch","paths":[…]}registers any number of roots per socket,
authorised exactly like a room subscribe (confinement, RBAC, tenant), always
acknowledged withwatching(the capability probe for older servers), and
delivered astree_changeframes with root-relative folders, coalesced like
rooms and never dropped on a full queue. Folder-size refresh frames are not
delivered to watches. See Realtime. -
Conditional uploads. The multipart upload and the staged commit accept
expect(none, or the<size>:<last_modified>a listing showed) and
answer412 PRECONDITION_FAILEDwithout writing when the file changed in
between. The sync engine sends it on every upload, so a browser save that
lands in the same second as a desktop save is kept beside it instead of
being replaced. See Uploads. -
A first sync that would re-upload a stale copy holds it and asks. With no
history, "new here" and "deleted on the server" look the same, and one client
put 9,665 cleaned-up files back on a server. A first run that would upload
more than 100 local-only files into a server folder that already has files
holds them (and any file that differs) and runs the rest:filex sync confirm <pair>sends them,filex sync discard <pair>moves them to the local sync
trash so the folder matches the server.sync list --jsoncarrieshold_new
/held; the desktop app shows the count with Upload them and Move to
local trash. (#35) -
Bandwidth limits and a sync window.
filex sync run --limit-down/
--limit-up <KiB/s>cap all transfers of a run together (the bodies are
paced, never the connection), and--window HH:MM-HH:MMonly syncs in that
part of the day — outside it nothing talks to the server, not even the change
stream, and a pass still busy when it closes stops cleanly and continues in
the next window. The desktop app offers both as presets in Settings. (#35) -
Transfer progress in bytes, with an estimate:
transfer: 120/11704 (1.2 GiB of 52.6 GiB, about 8h 10m left), printed at least every 5 seconds. The desktop
app shows it on each folder's line, in its own language. (#35) -
GET /api/files/manager?action=changes&path=…&since=<cursor>answers "has
anything under this folder changed since my cursor?" as{ cursor, changed },
from an in-memory change log fed by every write surface. Every doubt — no
cursor, a restart, a cursor older than the log — ischanged, and a change
counts only if the caller can see what it touched. (#35) -
Rescan one folder:
POST /api/admin/storages/{id}/sync?path=<folder>. The
same walk over one catalogued folder, synchronously, answering{path, scanned, added, updated, removed, reconciled}(504 after ten minutes). Only
rows inside the folder can be removed, a listing that failed part-way removes
nothing, and no sync-run row is written. (#35) -
Scan exclusions: tell a storage what not to catalogue. A new storage
setting, Paths to exclude from scanning (config.scan_exclude, on every
driver), takes glob patterns relative to the storage root, one per line:
*within a name,**across folders, and a pattern without a/names an
entry at any depth (.*skips every hidden file and folder,@eaDirevery
Synology thumbnail folder,*.tmpevery temp file), while/buildor
downloads/incomplete/**is anchored at the root. The scan does not go
into a matching folder — a.git, a.snapshotsor a download client's
incomplete/costs nothing — and nothing matching is catalogued, indexed,
thumbnailed or virus-scanned. One rule decides every walk that catalogues
(the full scan, the one-pass object-store listing, a folder rescan — which
refuses an excluded folder with 400 — the catalogue of a copied folder) and
thefsnotifywatcher, where a change to an excluded path no longer starts
a scan. A cost control, not an access control: the files stay on the
storage and are still served by path, over the file protocols and to the AI
tools. Rows catalogued before a pattern was added stay as they are and are
never moved to the trash for being unseen; filex's own names (.filex-open,
.keepdir, an encrypted folder's marker) are outside the patterns; a pattern
that would exclude everything, a!, a..or a broken glob is refused on
save withSCAN_EXCLUDE_INVALIDand a sentence naming the pattern in the
reader's language — as is an empty or/storage root (ROOT_PATH_FORBIDDEN,
which used to answer one English sentence inerror; the code stays there
and the sentence moved tomessage). Docs: STORAGE.md → Scan exclusions.
(#44) -
Desktop: Pause sync, in the tray menu and Settings, remembered across
restarts, reboots and the hidden start at sign-in. (#35) -
Desktop: the computer stays awake while sync moves files (the screen still
locks); an overnight first sync lost 1 h 40 min to idle sleep. (#35) -
The access log says who asked:
user_id,token_id(the row id, never the
secret) and, in multi-tenant mode,tenanton everymsg=httpline, plus
actionfor/api/files/manager(one of its own verbs, orother). The
query string is still never logged. (#35) -
A cut search result says so: both search responses carry
truncated, and
the explorer shows "More results than shown — narrow your search". (#35) -
Apps: the platform seal.
cert_issue {purpose: "platform"}hands an app
withsignthe installation's own seal: one key per tenant and app, kept by
the host (never destroyed —key_destroyrefuses it), CN filex document
seal, OU the app's name, issued by the live authority and re-issued by the
next one after a rotation; it signs throughhost_signfrom jobs only. The
signing app seals every completed request with it (the owner, 2026-09-22:
"the final bytes are sealed by filex itself"). SDK:pluginkit.PlatformSeal,
plugintest.Host.PlatformSeal. -
Apps: a lock until lifted, and a lock on the job's own output.
file_locktakesttl_days: -1(pluginkit.LockUntilLifted) — no end,
until the app or an administrator (audited) lifts it — and arefnaming one
of the job's OWN outputs, which is promised and taken when the output is
committed (never when the job fails). How the signing app keeps a finished
document locked for good, when the request asks for it.
This release has more to it than fits on one page. The rest of the
entry — and every earlier release — is in CHANGELOG.md.
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
Security advisories
- GHSA-gj84-9xx7-2g9f — an unsigned ONLYOFFICE save callback was accepted (High, 7.5)
- GHSA-55vh-f285-5wv3 — a restricted API token could issue credentials wider than itself (High, 8.8)