Repository navigation
Releases
UwUSync Server 0.4.0
Signing in with UwUAuth. A server paired with UwUAuth lets people make an account, and add a
device to one, by signing in there instead of with an invite or an enrolment token. The master
password and the account key stay what they were: UwUAuth says who somebody is, never what opens
their vault, and can neither read nor reset it. It needs UwUSSH 0.4; older clients sync as
before. How it works, and what an operator sets, is in docs/sso.md.
- Pairing from the command line.
uwusync-server uwuauth pair <address> <code>with a code
made in UwUAuth;uwuauth statussays what it is paired with,uwuauth unpairforgets it. The
OpenID Connect client and a hash of the SCIM token are kept in the database, and a running
server picks a pairing up at once. - The browser on the device, the secret on the server. The device opens the system browser
and listens on its own loopback address; the server is the OpenID Connect client, with PKCE,
state and nonce, and checks the ID token itself. What the device gets is a ticket for five
minutes and one use, bound to the device's own PKCE pair, that stands in for the invite. - SCIM. UwUAuth pushes who may sign in to
/scim/v2/Users. Somebody pushed and not yet signed
in is kept as pending; pushing somebody who already signed in updates them and answers with
the id they have.active: falseshuts all their devices out at once and keeps their
data;active: truelets them back in with their master password. Deleting somebody deletes
their account, unless they are the only owner of a team that has other members: that is
refused with 409 until another member is an owner. Groups are taken and ignored. - Discovery.
/uwu/v1/serverand/.well-known/uwusuitesay what this server is and whether
it signs in with UwUAuth. UWUSYNC_UWUAUTH_CAnames extra roots to trust for UwUAuth, for one behind a private CA.- Invites, enrolment tokens and open registration stay as they are;
UWUSYNC_REGISTRATION=closed
also refuses new accounts by UwUAuth. The database is upgraded in place at the first start; a
backup from 0.3 can be restored into 0.4, not the other way round.
UwUSync Server 0.3.0
Teams. A team is a vault that several accounts share: hosts, keys and snippets a group works
with together, sealed with a key only its members have. The server stores a team the way it
stores an account's own vault and can read as little of it. It needs a client that knows teams;
older ones sync as before. The protocol is in docs/teams.md, what an operator
sets in docs/deployment.md.
- Four roles. An owner does everything, an admin everything but touch owners or delete the
team, an editor reads and writes records, a reader reads. A team always keeps an owner. - Nobody gets in on a code alone. An invite is good for a week and for one account, and makes
a member who waits: they see nothing of the team until an owner or admin has confirmed them and
handed them the team key, wrapped to their own. The server keeps a hash of the code, and counts
wrong guesses. - Out means out. A removed member is refused from the next request on, and the team says that
its key wants changing. Rotating gives every remaining member a new key in one step and drops
everything sealed under the old one; a push under an old key is refused. - Yours to switch off.
UWUSYNC_TEAMS=offfor no teams,UWUSYNC_TEAM_CREATION=nonefor no
new ones,UWUSYNC_MAX_TEAMS_PER_ACCOUNTandUWUSYNC_MAX_TEAM_MEMBERSfor how many. A team
may hold what one account may hold, andUWUSYNC_SERVER_MAX_MBnow counts accounts and teams
together. - Three commands.
uwusync-server teamslists them,team-deletedeletes one, and
team-ownergives a team that lost its owner a new one. - What the server reads of a team is its name, the name each member goes by in it, and who is in
it in which role. The database is upgraded in place at the first start; a backup from 0.2 can
be restored into 0.3, not the other way round. GET /v1/eventshas a second event besiderecords:team, with a team's id, when one of the
account's teams changed — or with*when the stream fell behind and the client should look at
all of them.- A device replaces its account's member key only with the master password, as it changes the
password or revokes a device only with it. An invite stops working when the member who made it
is removed or may no longer invite. The nightly cleanup forgets the markers of deleted team
records as it does an account's: after 90 days, once every device that reads the team has seen
them.
UwUSync Server 0.2.2
The command assistant's records, for UwUSSH 0.3. UwUSSH 0.3 syncs the settings of its command
assistant and its cache of answers as two new kinds of record (assist_config and
assist_cache). The server stores them like any other record. Before, it refused a push that held
a kind it did not know.
- Old clients are not sent them. UwUSSH 0.2 cannot read a page that holds a kind it has never
heard of, so the assistant's records only go to clients that ask for them (assist=1). The
others are passed over, and the cursor still moves past them, so an older client keeps syncing
everything else and never gets stuck in front of them. - New kinds need no new server. A record's kind is now any short lowercase name, kept as it
came. Kinds the server does not know yet go to the same clients as the assistant's: those skip a
kind they cannot read instead of failing the page. - A conflict hands a record back under its own kind. Before, a kind the server could not name came
back as a host.
UwUSync Server 0.2.1
Security fixes. A third look, this time at 0.2.0, found four more ways to wear the server down
— none that let anybody read a record, sign in as someone else or reach another account. All four
are fixed, each with a test. The whole round is in
docs/security-review-2026-09.md.
- Idle connections close, HTTP/2 ones too. A connection with nothing to answer for fifteen
seconds is closed, whichever HTTP it speaks. Before, an HTTP/2 connection that answered the
pings stayed open for good, so a handful of addresses could take every connection slot. One
HTTP/2 connection now carries 8 requests at a time instead of 64. An event stream being
answered still counts as busy. - Limits are counted before a body is read. Creating an account, joining, pairing and pushing
are counted first, so a request that is over its limit no longer gets its body buffered. - One account has at most four pulls and four pushes going at once, each counted until its
answer has been sent, not just until it was started. The apps sync one request at a time and
never get near that. - Signing in is counted per device, and only loosely per address. Behind Docker's proxy every
IPv6 client shows up as one address, and one of them could lock all the others out.
docs/deployment.md now explains how to give IPv6 clients their own
address. - The limiter tables cannot be filled to lock others out. Every limit has a table of its own;
when one is full, a newcomer is counted under its network instead of being refused, and signing
in is never refused for want of room. With registration closed, creating an account is refused
before anything is counted.
.env is kept out of git and out of the image build, and the README says what the connection
limits really cover.
UwUSync Server 0.2.0
UwUSSH Server is UwUSync Server now. It syncs UwURDP as well as UwUSSH, so the old name had
become too narrow. The new names:
- Repository: MinifyX/UwUSync-Server. The old address forwards.
- Image:
ghcr.io/minifyx/uwusync-server. - Command:
uwusync-server. - Settings:
UWUSYNC_…. - New installations:
/opt/uwusync. - A new icon.
Nothing changes for the devices: the protocol, the setup codes and the pinned keys stay the same.
update.shmoves a server from before over by itself, in the directory it is in. It takes a
backup first. The settings in.envget their new names, the data volume stays the same one,
and the old container stops only once the new image is on the machine. If the new version does
not come up, everything from before goes back. CI tests this against a real 0.1.1 installation.- The old names keep working.
- The server still reads
UWUSSH_…settings, and its log says which ones still have the old
name. - A database called
uwussh.dbkeeps that name, so a rollback still finds it. - Backups called
uwussh-….dbare listed, restored and pruned like new ones. - The image answers to
uwussh-serveras well. It is also published under its old name,
ghcr.io/minifyx/uwussh-server, for acompose.yamlkept by hand.
- The server still reads
install.shstops on a machine that already has UwUSSH Server in/opt/uwussh, and says
how to bring that one over. It no longer sets up a second server next to it.
UwUSSH Server 0.1.1
Security fixes. A second look at 0.1.0 found ways to wear the server down, fill its disk, or
— from a directory someone else could write to — turn update.sh against its own machine.
Nothing that let anybody read a record or reach another account. Every one is fixed, most with a
test.
- Connections have deadlines and a ceiling. A client that opened a connection and then said
nothing, or sent its headers a byte at a time, or sat idle after its last request, held a socket
for as long as it liked; a few thousand of those were a server nobody else could reach. Now the
headers of a request get fifteen seconds, an idle connection fifteen more, HTTP/2 connections are
pinged and dropped when nobody answers, and the server takes 512 connections at once and 32 from
one address (UWUSSH_MAX_CONNECTIONS,UWUSSH_MAX_CONNECTIONS_PER_IP; behind a proxy only the
total counts). An event stream being answered is left alone. On Linux the server also raises its
own open-file limit as far as the system allows. - The disk cannot be filled through the backups. The quota was per account, and fourteen
nightly backups each copied all of it. Now all accounts together hold at most
UWUSSH_SERVER_MAX_MB(2 GiB by default), seven backups are kept instead of fourteen, and a
backup that would leave the disk without room to spare is skipped with a warning. At the
defaults, the database and its backups top out around 16 GiB. update.shonly trusts what only root can change. It looked for the server in the directory
the shell was in, and ran whatevercompose.yamland.envit found there. Now it looks beside
itself, in/opt/uwusshor where--dirsays; refuses a directory,compose.yaml,.envor
.uwussh-updatethat anybody but root (or the admin runningsudo) could write to, or that sits
under a directory someone else could; and refuses a.envthat setsCOMPOSE_…orDOCKER_…
variables.- A device holds at most eight session tokens. Signing in again and again piled up tokens in
memory without end; now the oldest goes, and expired ones are swept every few minutes. - A vault must be expensive to guess against. The server took a vault header asking for
Argon2id at 1 KiB and one pass. It now wants at least 19 MiB and two passes for a new account or
a new master password; the client uses 64 MiB and three. Vaults already on the server are not
touched.
Manifests, for UwUSSH 0.1.1. The client now has each device publish a sealed list of what it
holds, so a device can tell when a server serves it old versions or holds records back. The server
stores them like any other record; it hands them only to clients that ask (manifests=1), so an
older client keeps syncing, and it drops a device's manifest when the device is revoked. UwUSSH
0.1.1 and this server belong together: update both.
UwUSSH Server 0.1.0
The first release. A sync server for UwUSSH: your
hosts, keys and passwords on all your devices, through a machine that belongs to you and cannot
read a single field of what it keeps.
Setting it up is one command. install.sh installs Docker when it is missing, asks how your
devices reach the machine, starts the server and shows a setup code to paste into UwUSSH. No
domain, no certificate to get: the server makes its own and your devices pin its key, the way an
SSH client pins a host key. It runs just as well behind Caddy or nginx.
Updating is one command too. update.sh takes a newer copy of itself, backs up, pulls the new
image and waits for the server's health check — and if the new version does not come up, the one
from before goes back in. Once a day the server looks whether there is something newer and says so
in its log. Images for amd64 and arm64, as latest, beta and edge.
What is in it:
- Zero knowledge. Every record is sealed on the device, header and all, so the server cannot
read it, move it to another record, mark it deleted or hand an old version back as new. It hands
out sequence numbers, keeps the newest version of each record, and refuses a write based on a
version it no longer holds. - An account key next to the master password, so a copy of the server's database is not even
a place to start guessing the password. - Devices sign in with a key of their own, by signing a challenge. A new device joins from one
that is already in, with three spoken words: the two run SPAKE2 through the server, which carries
the messages and understands none of them. - Limits where guessing or hammering would pay, and on what one account may hold.
- Backups every night and before every update, and
uwussh-server restoreto put one back. - A distroless image that runs as an unprivileged user on a read-only file system, with no
capability at all.
Reviewed before release by someone who had not written it: nothing critical, and nothing that let
one account near another's data; every finding is fixed, each with a test. The details are in
docs/security-review-2026-09.md.
The app's side — Settings → Sync — comes with the next UwUSSH beta.