How it works · s5w

Security

Who has access to what, how it is checked and what is left to the deployment.

In short

Tokens per bucket

Every token works only in its own bucket. The database stores its hash, not its value.

Admin API closed from outside

With the panel on, the management API answers only the panel itself, inside the container.

Brute-force lockout

Five failed sign-ins within 15 minutes — and the address is locked for 5 minutes.

Signed links

Temporary links are signed with the server key: they cannot be forged or extended.

Access tokens

  • A token is issued for one bucket and gives access only to it.
  • The database stores the token’s hash. The value itself is shown once, when issued; it cannot be recovered — only reissued.
  • Two levels: BASIC (only the check and downloads with the token) and FULL (everything). Give BASIC to those who only need to read.
  • A token can be limited in time, disabled or reissued (rotation). After rotation the old value stops working at once.
  • Every request checks that the token is known, active, not expired and of a sufficient level. The answers are 401 (no token, or it is unknown) and 403 (disabled, expired, not enough rights).

Admin panel sign-in

There is one account, set by the ADMIN_LOGIN and ADMIN_PASSWORD variables.

  • Without them, sign-in is closed completely. This is by design: a panel without credentials opens to no one, rather than to everyone.
  • The session lives 24 hours. It is kept in the s5w_admin cookie with the HttpOnly and SameSite=Strict flags: a script on the page cannot read it, and another site cannot send a request on the administrator’s behalf.
  • The session is signed with the WINTER_KEY key and bound to the login and password. Changing the password or the key ends every open session at once.
  • Brute-force lockout. Five failed sign-ins from one address within 15 minutes lock that address for 5 minutes: sign-in answers 429 with a Retry-After header, even with the right password. The counter is kept in memory and resets on restart.
  • The client address behind a proxy is taken from X-Forwarded-For, but only from trusted proxies (TRUSTED_PROXIES). Otherwise anyone could send someone else’s address and get around the lockout.

The default password

The examples use change-me. In production set a long random password and keep it as a secret, not in a plain compose file in the repository.

Admin API

The management API /admin/… creates buckets, tokens and everything else — it is full access to the storage.

  • With the panel on (the default) it accepts requests only from inside the container, that is, from the panel itself. From outside — 404, as if it did not exist.
  • ADMIN_API=public opens it to the outside — for automation scripts, for example. Then close port 9090 or the /admin path on the proxy for everyone but the addresses that need it.
  • Without the panel (ADMIN_UI=false) it is open by default — otherwise there would be nothing to manage buckets with. Close /admin at the network level.

File visibility

A file is either public or not; the folder decides.

  • The bucket root is always public. A file uploaded or moved there opens at /o/{bucketId}/{id} without a token.
  • A file in a folder takes the folder’s visibility. When moved between folders, its visibility changes to that of the new folder.
  • The public address contains the bucket id and a random file id (96 bits of randomness) — it cannot be guessed by brute force. But whoever got the link can pass it on.

Moving to the root

A private document moved to the root becomes available to anyone who has the address. Keep private files in private folders.

  • A link is signed with the WINTER_KEY key: the expiry, the file and the parameters are part of the signature and cannot be changed.
  • A tracked link (revocable or maxDownloads) can be revoked on its own.
  • All links of a bucket, signed ones included, are revoked by a single DELETE /v1/links request or a button in the panel.
  • Changing WINTER_KEY voids every temporary link in every bucket.

Uploaded content

  • HTML and SVG never open in the browser — they are only downloaded. Shown by the browser, they could run a script on behalf of your domain.
  • Only images, video, audio, PDF and plain text open in the browser; everything else is served as a download.
  • The file name is cleaned: path separators and control characters are replaced with -. The name goes into the Content-Disposition header escaped, so no extra header can be injected through it.
  • Content reaches the disk under its hash: the name from the client takes no part in paths.

What is left to the deployment

  • TLS. S5W itself speaks HTTP. Encryption is done by the proxy in front of it — nginx-proxy-manager, traefik, nginx.
  • Network access. Do not publish ports you do not need. The panel domain is worth closing further with an access list or basic auth.
  • Secrets. Keep ADMIN_PASSWORD and, if you set it yourself, WINTER_KEY as secrets.
  • Volume backups — they contain every file and the signing key; protect them as you protect the data itself.