Repository navigation
v0.31.0
filex v0.31.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.31.0
docker pull ghcr.io/brf-tech/filex:full-v0.31.0What changed
Added
-
Encrypted folders can be recovered. Until now, forgetting the password
meant the files were gone — that was the documented behaviour and it was a
bad one. Creating an encrypted folder now shows a recovery key once;
filex never stores it and it opens the folder without the password.The file format did not change. Each file's key is still wrapped by exactly
one key in its 97-byte header — that key is now the folder's master key
rather than the password key, and the marker holds the master key wrapped
once per way in. For a folder created before this release the two are the
same thing, so existing encrypted files are byte-identical and open
unchanged; there is a round-trip test against a frozen copy of the v0.30.1
crypto module, in both directions, because that is the promise that matters
most here. Such a folder cannot be given a recovery key by the server — it
has no password to re-wrap with — so filex offers the upgrade at the one
moment it holds one: the next successful unlock, behind a visible notice. -
Optional operator escrow, fixed at install.
FILEX_INSTALLATION_E2E_ESCROW_KEY
adds a second way into every folder created while it is set. It is RSA-OAEP:
filex holds only the public half,filex e2e-escrow keygenprints the private
half once and writes it nowhere, and the operator supplies it back when they
need it. A stolen database therefore decrypts nothing.⚠ This is a backdoor, deliberately, and the documentation says so. When
the escrow key is used through filex the folder's owner is notified, and a
forged "escrow was used" report is refused — the client must first decrypt a
server-issued nonce sealed to the escrow key. But the notification is an
announcement, not a control: an operator holding the private key can copy the
marker and the ciphertext off disk and decrypt offline, with no request, no
notification and no audit row, and filex cannot detect it. If that is not
acceptable for your deployment, leave escrow off.FILEX_INSTALLATION_is a new prefix for settings that are fixed when the
data directory is initialised. filex refuses to start if one of them changed,
because for escrow the immutability is arithmetic rather than policy: a folder
created while escrow was off has no escrow-wrapped key, and switching it on
later cannot open it. -
Star is a real action. It was rendered in the list view and nowhere else,
so v0.30.0 shipped a Starred view that a user in grid view had no way to fill.
It is now in the context menu ("Star" / "Unstar", multi-selection aware), on
grid and gallery cards (on hover or focus, and painted permanently once
starred, so the Starred view is legible without hovering every tile), and on
the keyboard as a remappableS. -
Tags in the navigation panel. Tagging has existed for a long time and
there was no way to browse by tag inside the explorer — only an admin page.
The panel now lists the tags that exist and opens the files carrying one.
Fixed
-
A virtual view opened from its own URL said "Folder not found". The
explorer writes the current location into the address bar, so opening Trash
put#.trashthere — and on reload that sentinel was handed to the ordinary
folder loader, which 404'd. Reported for Trash; Recent, Starred and Shared
behaved the same way. Sentinels are now routed to their view before the
request is made, so a reload lands back in the view with its own empty state,
and an unknown dot-path is left alone because a user may own.config. -
The details panel printed
.starred. Third surface of the same bug that
put.sharedin the tab strip in v0.30.0: the sentinel-to-label map had been
written more than once. Every surface that renders a path segment now reads
one map. -
An app token could manage its owner's credentials. An API token
authenticates as its owner, and the embeds we run authenticate every visitor
with one shared token injected by the host's proxy — so v0.30.0's "API keys"
panel entry meant an embed visitor could list and revoke the credential the
embed itself runs on, and mint S3, SSH and NFS credentials as the owner.A token now declares what it is.
useris a person's own credential and
nothing changes for it;appis an integration, and the four credential
surfaces (/api/tokens,/api/auth/s3-keys,/api/auth/ssh-keys,
/api/auth/nfs-exports) refuse it with a 403 that names the token and both
ways out. The explorer leaves out the surfaces that belong to a single person
— Recent, Starred, Shared with me, API keys — while Upload, the storages,
Trash, Tags and "How to connect" stay. Existing tokens migrate toapp,
because the restricting direction is the safe default and these surfaces only
matter when a browser UI is drawn. -
/api/files/capabilitiescould refuse a caller. Gating it on the token
chain meant a revoked token, an unknown token username or a disabled account's
cookie got a 403 from a public route — the login screen failing closed. It now
annotates the caller when it can and answers everyone. -
Moving a plaintext file into an encrypted folder is refused. Uploads were
encrypted client-side; move, copy and paste were plain server-side byte
operations, so filex's own UI would put plaintext inside an encrypted folder
with no warning. The guard is server-side and covers the ops queue, sync moves
and the AI/MCP surface. -
The install banner ate clicks. Both banners are full-width fixed strips
that paint a centred card, and withoutpointer-events-nonethe empty half
sat on top of the sidebar behind it: measured, five destinations unreachable
at 1440×900 and eleven on a taller menu.PendingOpsTray, the same shape in
the same corner, already did this correctly. -
slimwas not slim. The tag was built from the full recipe, so
docs/DOCKER.mdpromised ~40 MB while the registry served 511 MB
compressed —latest,slimandfullwere the same image, and the two
Dockerfiles differed by one package that nothing calls. There is now a real
slim image: 43 MB compressed, the binary and the embedded UI, with image
thumbnails (pure Go) still working and everything that shells out to ffmpeg,
ghostscript or libreoffice reported as unavailable rather than failing. -
docs/E2E-ENCRYPTION.mdwas 183 lines of Turkish in an English repo,
linked from the README and published on the docs site. Translated, and
corrected against the code while translating: it under-counted the places that
filter the marker, missed two disabled surfaces and the refusal to nest
encrypted folders, and its "v2 roadmap" listed six things none of which had
shipped — those are now stated as limitations rather than promises.
Changed
-
Docker images build natively per architecture. arm64 was emulated, and the
emulated part wasapk add libreofficeplus a JRE — the worst possible thing
to run under QEMU. Each architecture now builds on its own runner and the tags
are joined from the digests, so no tag exists until both have landed. The
docker job also no longer waits for goreleaser: it builds its own binary from
source and took nothing from the Release, so the dependency only lengthened
the critical path. -
The browser suite is a gate. Cypress defaulted to
the live deployment's URL — and ran in no pipeline. It now boots its own instance
and runs in CI. Getting there meant fixing 19 red specs, several of which had
been passing for the wrong reason: one matched the sidebar link instead of the
dashboard it was meant to assert on, and two asserted a capability slot that
only existed because production still carried a row from an older version.
227 of 227 pass, and the run went from 9m58s to 1m25s once the service worker
stopped re-precaching the bundle between tests.
Added
-
Encrypted folders can be recovered. Until now, forgetting the password to
an E2E-encrypted folder destroyed it — the documentation said so, and it was
true. Every folder created from this release on gets a user recovery key:
160 bits, shown exactly once when the folder is created, never stored by
filex, and enough to open the folder without the password. It is a password
equivalent, so keep it somewhere other than the password.The file format did not change. A per-file key was already wrapped by one
folder key; that key is now a folder master key held in the marker,
wrapped once per way of reaching it. Adding a recovery path costs one more
wrapped copy of 32 bytes, not a re-encrypt of anything — which is why not a
byte of anyone's existing data was touched. -
Optional key escrow for operators, fixed at install time via
FILEX_INSTALLATION_E2E_ESCROW_KEY.filex e2e-escrow keygenmints the pair;
the server gets the public half only, so it can seal new folders to the
escrow identity and open nothing. The private half is the operator's, and a
stolen filex database still decrypts nothing. Using the escrow key notifies
the folder's owner, and the notification is evidence: the client must
decrypt a server-issued challenge sealed to the escrow key before the event
is recorded, so it cannot be forged — and, stated plainly in the docs, cannot
be relied on either, because an operator holding the private key can decrypt
offline without ever asking filex. -
Install-time settings, as a convention. Anything prefixed
FILEX_INSTALLATION_is recorded on the first boot and frozen; filex refuses
to start if it later disagrees with the environment, and says what changed and
what the operator can and cannot do about it. Escrow is the first of these,
and the reason is arithmetic rather than policy: a folder created while escrow
was off carries no escrow-wrapped key, so switching it on later cannot open
it, and a server that started anyway would be claiming a capability it has
over only half its data.
Fixed
-
filex's own UI would put a plaintext file inside an encrypted folder.
Uploads are intercepted and encrypted in the browser, but paste,
drag-and-drop and duplicate are server-side byte copies that never touch the
crypto — so a file dragged into an encrypted folder was stored exactly as it
arrived, looked like its encrypted neighbours in the listing, and nothing
warned anyone. The reverse was as bad: a file moved out stayed encrypted
somewhere no password prompt would ever appear.The server cannot fix either by encrypting or decrypting, because it has no
key, so it refuses: a copy or move may not cross an encryption boundary
(HTTP 409, naming the file). The rule lives in one place and every transfer
surface calls it — the ops queue, the synchronous move and the AI/MCP move —
rather than in the one client that happened to notice.
Changed
-
Folders created before this release keep working, untouched. Their format
is read as a first-class path, not a migration shim, and the test suite proves
it against a frozen copy of the v0.30.1 module rather than a re-creation of
it. They cannot be given a recovery key by the server, because that needs the
folder password and filex does not have it — so filex asks at the one moment
it does: the next successful unlock. The offer is a visible strip with the
consequences spelled out, including that accepting on an escrow-enabled
installation also gives the operator a key. Declining changes nothing. -
The create-folder warning no longer says recovery is impossible, because for
new folders it is not.