Repository navigation
Releases: KervanServer/kervan
Release list
Kervan v0.9.0
Security
-
Go 1.26.9 and
golang.org/x/netv0.60.0.govulncheckreported ten
standard-library vulnerabilities reachable from Kervan's code: HTTP/2 and
HTTP/1 desynchronisation and DoS issues innet/http, unbounded Range
header parsing, MIME header memory limits, and malformed TLS ECH
extensions. All are fixed by these versions; the container build uses
Go 1.26.9 too. -
Concurrent sign-ins could exhaust memory. Every password check runs
Argon2id with 64 MiB, and nothing limited how many ran at once. 300
simultaneous wrong-password logins for a known username drove the server
to 13.7 GB before any lockout or IP ban reacted. Argon2 work now runs
within a 256 MiB budget; further checks wait up to 30 s, then fail. The
same flood now peaks at about 540 MB (about 410 MB with
GOMEMLIMIT=400MiB).
Added
- File browser. Download and preview buttons: text and code files up
to 1 MiB, images up to 20 MiB. Several files can be uploaded at once by
picker or by dropping them on the browser, with a progress counter.
Sizes are human-readable and a Modified column was added.
Fixed
- Regular users got a broken WebUI. Their landing page was the admin
dashboard, which stayed on loading placeholders, and the menu offered
admin pages (Users, Groups, Monitoring, Configuration) that could only
fail. Non-admins now land on Files, see only the pages they can use, and
are redirected from admin routes. Two-factor setup moved from the
dashboard to My Account, so every password user can reach it.
Changed
/api/v1/audit/eventsand/api/v1/transfersincludepaginationeven
when auditing or transfer tracking is off./api/v1/auth/totpanswers 405 for methods it does not support while
two-factor auth is disabled (it returned 400).
Kervan v0.8.0
Added
-
Password-protected share links. Links can require a password (at
least 6 characters, stored as an Argon2id hash) and the WebUI now sets
the download limit too. Share URLs open a public landing page
(/s/<token>) that shows the file name, size, expiry and remaining
downloads, and asks for the password when one is set. Wrong passwords
count toward the per-address login ban. Scripts can use
/api/v1/share/<token>with anX-Share-Passwordheader. Creating,
revoking and downloading through share links is audited (share.*);
the audit record holds only a short token prefix. -
Audit filters.
/api/v1/audit/eventsand/api/v1/audit/export
acceptfromandto(RFC 3339 orYYYY-MM-DD). The Audit page now
filters by date, event type, status, user and text, pages through
results, shows user, protocol, address and details for each event, and
exports with the same filters. -
MCP tools.
get_user(sign-in restrictions, SSH key fingerprints,
effective policy),list_groupsandverify_audit(checks the
tamper-evident chain).audit_querynow also filters bystatus,
fromandto. The README gains an MCP section. -
Fuzz targets for the SFTP packet handler, SCP sink and exec parser,
the FTP command loop and its path/command parsers (go test -fuzz).
go testruns their seed corpora, including the crashers found so far. -
Interop suite.
make interopruns lftp (FTP, explicit and implicit
FTPS, SFTP), rclone (FTP, FTPS, SFTP) and paramiko against a running
server: 26 checks. -
Docs.
docs/openapi.yaml(OpenAPI 3.1 reference for the REST API),
CONTRIBUTING.md(workflow, architecture map, conventions, release
steps), and an up-to-date.project/ROADMAP.md.
Fixed
- FTPS uploads from lftp (GnuTLS) failed with
426and empty files.
Under TLS 1.3 the server sent session tickets on the data connection that
the client never read, so its close turned into a TCP reset that dropped
the upload. Data connections no longer issue session tickets. - An SFTP write at a huge offset crashed the server when the user's
storage was the memory backend: the backend tried to allocate the whole
gap. The memory backend now refuses gaps over 64 MiB and files over
4 GiB, writes in place instead of copying the file on every write, and
rejects overflowing seeks. - A panic in an SFTP/SCP channel ended the whole process, because the
channel goroutine had no recovery. It now ends only that channel and is
logged. - SFTP packets declaring a large length (up to 16 MiB) were allocated
in full before any data arrived. Buffers above 512 KiB now grow only as
the data actually arrives. - Failed FTP transfers are now logged with the underlying error.
- Admin file actions were audited under the wrong user. When an admin
worked on another user's files through/api/v1/files/{user}/…, the
audit record named the owner as the actor. It now names the admin, with
the owner inmeta.target_user. - The WebSocket ignored sign-in restrictions. It now applies expiry,
allowed addresses and allowed protocols at connect and before every
snapshot, so a disabled or expired account loses its live stream too. - Client-sent
X-Auth-*headers are now stripped before routing. They
were already overwritten on authenticated routes, but public routes could
see them. - Password-hash export (
/api/v1/users/export?include_password_hashes=1)
now requires a signed-in admin session; ausers:readAPI key gets 403.
Every user export is audited (admin.user.export). - Config patches can no longer change fields the config view redacts,
such asstorage.backends.*.options.access_key. - Existence probing. Another user's session or share link now returns
404, like a missing one, instead of 403. Deleting an unknown API key
returns 404 (was 400). An admin naming an unknown user under
/api/v1/files/{user}/gets 404 (was 500). GET /api/v1/server/statusrejects other methods with 405.- Audit export validates its filters before reading the log, and streams
the log, keeping only matching events. A CSV export cut short no longer
tries to write a JSON error after the headers were sent. completed_atis omitted for transfers still in progress (it showed the
zero time).
Changed
- Share-link
share_urlis now the landing page (/s/<token>). The direct
download isdownload_url. Existing/api/v1/share/<token>links keep
working.
Kervan v0.7.0
Added
-
Per-user sign-in restrictions. Each user can have an expiry time,
allowed IP/CIDR ranges, allowed protocols (ftp,ftps,sftp,scp,
webui) and a cap on concurrent FTP/SFTP/SCP sessions. Set them from the
Users page, the users API, or the newkervan user accesscommand. They
apply on every protocol and to existing WebUI/API sessions. Every 30 s,
open FTP/SFTP/SCP connections of expired, disabled or deleted accounts,
or from addresses no longer allowed, are ended and audited as
session.revoked. -
Per-address connection limit.
security.max_connections_per_ip
caps concurrent FTP and SSH connections from one address, counted across
both protocols. -
Wider audit coverage. Sign-ins through the WebUI and OIDC (both
success and failure), password changes, user, group and API-key
management, config updates and reloads, session kills, and file
operations in the WebUI file browser are now audited. Directory
create/delete and renames are audited over FTP and SFTP too, and renames
record the destination inmeta.to. Admin events name the affected
account inmeta.target_user; passwords are never logged. -
FTP commands. New:
ABOR(including during a transfer, Telnet- or
urgent-data-framed),STOU,MFMT,SITE CHMOD/SITE HELP,STAT,
HELP,MODE,STRU,ALLO, and theX*legacy aliases. FTP
upload/download audit events now includemeta.bytes, and failed or
aborted transfers are audited. -
SFTP extensions.
posix-rename,statvfs/fstatvfs(dfshows the
quota),fsyncandlimits(OpenSSH clients switch to 255 KiB
requests). Local storage now reports real disk space.
Changed
- SFTP sessions are capped at 1024 open handles.
- One SFTP audit event per transfer. SFTP used to write a
file.write
event for every WRITE packet (thousands for one large upload). It now
writes onefile.writeorfile.readevent when the handle closes, with
the byte count inmeta.bytes. A transfer cut off by a dropped
connection is recorded with statusincomplete.
Kervan v0.6.0
Added
-
File-count quota.
quota.default_max_filesis now enforced, with
per-group and per-usermax_filesoverrides (API, CLI
--max-files, WebUI). Only regular files count, and overwriting a file
does not. -
Bandwidth limits.
max_bandwidthcan be set per user and per group,
withbandwidth.default_user_rateas the default and
bandwidth.max_totalas a server-wide cap. Limits apply to FTP, SFTP,
SCP and API transfers, are shared across a user's parallel connections,
and reload at runtime. Measured at 1.0 MiB/s for a 1 MiB/s limit over FTP
and SFTP. -
Self-service account. A new WebUI My Account page and
/api/v1/accountendpoints show a user's effective policy, and let them
change their password (local accounts) and manage their SSH public keys.
Previously neither was possible without the server CLI. -
Admin password reset and SSH key editing from the WebUI Users dialog
andPUT /api/v1/users(password,authorized_keys).
Changed
quota.default_max_filesnow defaults to 0 (unlimited). Configs
generated by olderkervan initversions contain
default_max_files: 100000, which had no effect before and now applies.
Set it to0to keep the previous behavior.
Security
- Session revocation: session tokens now carry
iat, and a password
change or reset revokes every WebUI/API session issued before it. An
admin reset also ends the user's protocol sessions. - SSH keys: keys with
authorized_keysoptions are rejected by the API,
and skipped with a warning bykervan migrate ssh-keys. Kervan does not
enforce the options, so afrom=restriction would have been silently
lost.
Fixed
-
quota: renaming a file onto an existing one now releases the
replaced file's bytes. Before, they stayed counted against the user's
quota. -
WebUI: an expired or revoked session now returns to the sign-in screen
with an explanation, instead of failing every request with "Session
expired".
Container image: docker pull ghcr.io/kervanserver/kervan:0.6.0 (linux/amd64, linux/arm64).
Checksums are in checksums.txt. Verify with sha256sum -c --ignore-missing checksums.txt.
Kervan v0.5.0
Added
- audit: size-based rotation and retention for file outputs
(max_size_mb, default 100;max_backups, default 10). Previously the
audit log grew without bound.- The HMAC chain continues across rotated files. Retention seals an
audit.prunedrecord before deleting a file, sokervan audit verify
can tell retention from tampering. - The API, export, MCP, verify and backup/restore cover the rotated
files. Restore validates rotated file names.
- The HMAC chain continues across rotated files. Retention seals an
Fixed
- audit: export and the MCP recent-events reader no longer load the
whole log into memory.kervan mcpnow reads the configured audit path
instead of always<data_dir>/audit.jsonl.
Container image: docker pull ghcr.io/kervanserver/kervan:0.5.0 (linux/amd64, linux/arm64).
Checksums are in checksums.txt. Verify with sha256sum -c --ignore-missing checksums.txt.
Kervan v0.4.0
Added
- audit: tamper-evident file logs, on by default
(audit.integrity). Each record is HMAC-chained to the previous one,
using a per-installation key in<data_dir>/audit.key.kervan audit verifydetects modified, deleted, inserted and
reordered records, and exits non-zero on any finding.- The line format stays backward compatible: the chain is an extra
chainfield. - The chain continues across restarts, and backups include the key.
Container image: docker pull ghcr.io/kervanserver/kervan:0.4.0 (linux/amd64, linux/arm64).
Checksums are in checksums.txt. Verify with sha256sum -c --ignore-missing checksums.txt.
Kervan v0.3.0
Added
-
FTP: virtual hosts through RFC 7151
HOST(or the TLS server name
for FTPS). Each host has its own banner and an optional group
restriction, so several tenants can share one address. -
audit:
syslogoutput (RFC 5424) over UDP, TCP, TLS or Unix sockets.
It supportsformat: ceffor SIEMs, configurable facility, RFC 6587
framing with automatic reconnects, and escaping that prevents log
injection.
Fixed
- FTP:
FEATreply lines start with a space as RFC 2389 requires. They
were previously stripped, which some clients reject.
Changed
- WebUI live updates are event-driven. Sessions, transfers and audit
events now push a WebSocket snapshot within about 200 ms of a change.
Bursts collapse into one frame, and an idle connection gets a 15 s
heartbeat instead of a frame every 2 s. The message format is unchanged.
Container image: docker pull ghcr.io/kervanserver/kervan:0.3.0 (linux/amd64, linux/arm64).
Checksums are in checksums.txt. Verify with sha256sum -c --ignore-missing checksums.txt.
Kervan v0.2.0
Added
-
OIDC single sign-on for the WebUI with any OpenID Connect provider.
Uses discovery, the authorization-code flow with PKCE, and JWKS-verified
ID tokens (RS/PS/ES/EdDSA;noneand HMAC are rejected); the
implementation is standard library only.- Accounts are provisioned on first sign-in and bound to the provider
subject. Local and LDAP accounts cannot be taken over. allowed_groupsrestricts who may sign in,admin_groupssyncs the
role, andgroup_mappingmaps provider groups to Kervan groups.- The session reaches the SPA through a one-time code, never in a URL.
- Verified against Dex in a real browser.
- Accounts are provisioned on first sign-in and bound to the provider
-
API:
Startnow binds the WebUI/API port synchronously, so a busy
port fails startup instead of only being logged. -
groups: permission and storage-quota templates. A user's primary group
supplies permissions and quota for every protocol and the API, with
per-user overrides (custom_permissions,max_storage). Per-user quotas
are new: previously every user hadquota.default_max_storage.- Managed through
/api/v1/groups,kervan group list|create|delete
(andkervan user create --group), and a new WebUI Groups page. - The Users page gains an Edit dialog for group, quota and permissions.
- Renames carry memberships along; deleting a group that has members
needsforce. - User import/export carries
primary_groupandmax_storage.
- Managed through
Container image: docker pull ghcr.io/kervanserver/kervan:0.2.0 (linux/amd64, linux/arm64).
Checksums are in checksums.txt. Verify with sha256sum -c --ignore-missing checksums.txt.
Kervan v0.1.1
Added
- SCP: recursive legacy copies (
scp -O -r) in both directions. Upload
follows OpenSSH semantics: a missing target becomes the copied directory,
and an existing one receives it as a subdirectory. Directory times are
kept with-p, a directory is refused without-r, and nesting is capped
at 128 levels. Covered by an OpenSSH interop test. - release: the Release workflow publishes multi-arch (
linux/amd64,
linux/arm64) images toghcr.io/kervanserver/kervan. The Dockerfile
cross-compiles on the build host, so only the final stage is emulated.
Container image: docker pull ghcr.io/kervanserver/kervan:0.1.1 (linux/amd64, linux/arm64).
Checksums are in checksums.txt. Verify with sha256sum -c --ignore-missing checksums.txt.
Kervan v0.1.0
Verified end to end against a running server with real clients: OpenSSH
sftp/scp (SFTP and legacy modes), curl (FTP in EPSV/PASV/PORT/EPRT, FTPS
explicit/implicit, SFTP, SCP), a headless browser for the WebUI, and the
Docker image. Those client runs are now part of the test suite
(internal/server/interop_test.go).
Fixed
- SFTP: reverted the v0.0.2 DATA/NAME packet-type swap. The draft and
OpenSSH use DATA=103 and NAME=104; v0.0.2 broke every real client
("Expected SSH2_FXP_NAME(104) packet, got 103") while the wire tests,
which encoded the same misreading, stayed green. - SFTP: SETSTAT/FSETSTAT are implemented (truncate; best-effort
permissions/times). OpenSSH 9+scpfailed every upload with
"remote fsetstat: Operation unsupported". - SFTP: the session sends
exit-status, soscpno longer exits 1 after
a successful transfer. - SSH: authorized_keys entries with a comment or options now match.
Before this, only keys imported throughmigrate ssh-keysworked. - SCP: shell-quoted targets are unquoted, so curl and libssh2 uploads no
longer land in a file literally named'/path'.-psends theTtime
record that libssh2 requires and applies incoming times. Completed
transfers are no longer failed when libssh2 closes without the final ack.
Ambiguous multi-word targets are refused, as OpenSSH does. - store: the JSON store is safe to share between the server and CLI
commands. Writes are locked read-modify-writes of the file, and reads pick
up external changes. Before this, a user created withkervan user create
against a running server could not log in, and the server's next write
silently deleted it. - WebUI: live updates work in browsers. The server rejected the
auth.<token>WebSocket subprotocol that the WebUI (and README) use, so
the dashboard was stuck in snapshot mode. - WebUI: page content now starts at the top of the viewport instead of
one screen height down, and the compact sidebar links are styled again.
A Radix tooltip had stringified NavLink's className callback. - config:
ftps.enabled: falsedisables FTPS even when a certificate is
configured.audit.enabled: falsedisables the audit sinks. - CI: fixed the gofmt and staticcheck failures on master. The
cross-protocol tests use ephemeral ports instead of fixed ones.
Added
- security:
security.allowed_ips/denied_ipsare enforced on FTP,
FTPS, SFTP/SCP and the API, and can be reloaded at runtime.
ftp.max_connections/sftp.max_connectionsare enforced. Over-limit
FTP clients get421and the rejection is audited. - security: a per-address login ban (
ip_ban_threshold,
ip_ban_duration,whitelist_ips) shared by FTP, SFTP and the API, with
IPv6 grouped per /64.brute_force.enablednow also controls account
lockout, and everybrute_force.*key is reloadable. - security:
auth.require_special_charis enforced, CSP (hash-pinned
inline script) and HSTS (on TLS) headers are sent, andftps.client_auth
/client_ca_fileadd client-certificate verification for FTPS. - FTP:
EPSV,PORT/EPRT(withftp.active_modeand bounce
protection),RESTresume forRETR/STOR,CDUP. - ops:
server.pid_fileis written and removed on shutdown, and a
startup warning is logged for settings that have no effect. Session
last_seen_atis updated on activity, accept loops back off on errors,
and the health checkcobaltdbwas renamed tostore. - CI: a
go test -racejob.
Security
- deps: Go toolchain go1.26.2 -> go1.26.8 and x/crypto v0.57.0,
x/net v0.59.0, x/text v0.42.0, x/sys v0.48.0. govulncheck had reported
22 reachable vulnerabilities, including in the SSH stack and in
crypto/tls, crypto/x509 and net/http; it now reports 0. - WebUI deps: react-router 7.18 (the only affected runtime package),
vite and related build tooling, and vitest 5. npm audit had reported 14
advisories (2 critical); it now reports 0.
Changed
- CI: GitHub Actions CI runs only on manual dispatch
(gh workflow run CI). Usemake checklocally for the same checks and
make auditfor vulnerability scans.
Removed
- Build and coverage artifacts that had been committed (
dist/,
kervan.exe, root-level coverage profiles).
Checksums are in checksums.txt. Verify with sha256sum -c --ignore-missing checksums.txt.