<!-- MIT License. Copyright (c) 2026 Decentralized Science Labs LLC. -->
<!-- summary: The operator's security checklist for a COLLAB server: the admin token, patches, the firewall, backups, logs, certificates, and the server kit's threat model. -->
# Operator security checklist

This page is the operator's checklist for keeping a COLLAB server safe, followed by a short account
of how the server kit is built to limit the damage of a mistake or a break-in. The full threat model
is in [Security](SECURITY.md#the-server-kit). The server kit, like the rest of COLLAB 2.2, had
AI-assisted reviews and no external audit.

## The checklist

1. Keep the admin token secret, and replace it after every staff change.
2. Keep the operating system patched, and open only the ports the site needs.
3. Back up `/etc/collab` and the settings, and keep the backup private.
4. Watch the logs and the certificate's expiry.
5. Leave the kit's files to the kit: change the server through the installer and the admin page.
6. Check what you publish, and let others check it.

## The admin token

The admin token decides which contracts every page of the site uses and what the site shows. Treat
it like a key.
- **Where it lives.** In `/etc/collab/collab.env`, readable by root and the `collab` user only. The
  installer prints it in the summary of every run; `sudo collab admin-token` shows it again.
- **Keep it out of chats and tickets.** A password manager can keep it: the sign-in form is made for
  that.
- **Replace it** with `sudo collab admin-token --new` when someone who knew it leaves, when it may
  have leaked, or when the admin service's log shows failed sign-ins you do not know. A new token ends
  every session at once.
- **Sign out** on a shared computer. Sessions last 12 hours, in a cookie that only the admin page's
  service reads.
- **Use the main host over HTTPS.** The admin page refuses requests from any other origin.

## The server

- **Patches.** Keep Debian or Ubuntu up to date (`sudo apt update && sudo apt upgrade`), nginx,
  OpenSSL and Python included. The `unattended-upgrades` package does it by itself for security
  updates.
- **The firewall.** Open TCP 80 and 443 to the internet, and SSH to the addresses you work from.
  Never open the admin service's port (8411): it serves 127.0.0.1 only.
- **SSH.** Sign in with keys rather than passwords, and keep root's and the sudo users' keys few.
- **Behind a proxy** such as Cloudflare's, set up nginx's `real_ip` module for it, so that the admin
  page's sign-in limits count visitors, not the proxy.

## Backups

Back up `/etc/collab` and the settings, as [Upgrades, rollbacks and backups](upgrades.md#backups)
shows. The backup holds the admin token and the two secrets: keep it as private as the server, and
replace the token if a backup is lost.

## Logs and certificates

- `sudo collab logs` and `sudo journalctl -u collab-admin` show every sign-in, every failed one, and
  every publish and settings change, each with its address. The token never appears in them.
- Status shows the certificate's days left; certbot renews it about 30 days before it ends. A
  certificate below 21 days means renewals are failing: see
  [Troubleshooting](troubleshooting.md#certbot-cannot-get-or-renew-the-certificate).
- Status also shows the prerender job's warnings and failures, and the disk.

[Status, logs and the collab command](monitoring.md) has what to look at, and how often.

## Leave the kit's files to the kit

The installer writes its files again on every run, and the services check what they read. Changes
made by hand are lost, or refused:

| File | Change it with |
|---|---|
| `/etc/collab/collab.env` | the installer's options; `sudo collab admin-token --new` for the token |
| `/etc/nginx/sites-available/collab.conf` | the installer's options; only `collab apply` touches its policy line in between |
| `/etc/systemd/system/collab-*` | nothing, or a systemd drop-in (`sudo systemctl edit …`), which the installer leaves alone |
| `/var/www/collab/releases/` | a release archive and its installer. The policy step refuses a release that is not root's alone |
| `shared/config.js`, `site.json`, `deployments/29.json` | the admin page's Publish and Site settings tabs |

Other sites' files and nginx's own configuration are yours. The installer never touches them.

## What you publish

- **Check the archive** before installing: `sha256sum -c collab-2.2.0.tar.gz.sha256`.
- **Check the deployment** before and after publishing, with the Check tab. Anyone can run it on your
  site, without signing in, to confirm that the pages use genuine contracts of this release.
- **Verify tokens with care.** A verified token carries this site's badge; check its contract
  address with its issuer first.
- **Publish your checksums.** The release's `SHA256SUMS` lets others confirm that you serve the
  release's files. The prerender job's output differs by design, and so does the policy line of
  every page once a setting has changed the security policy (an analytics ID, other RPC endpoints).

## The kit's threat model in short

### Who runs what

| Part | Runs as | May write | Talks to |
|---|---|---|---|
| nginx | its workers (`www-data`) | nothing of COLLAB's | visitors; the admin service on 127.0.0.1 |
| The admin service | `collab` | `shared/` only | 127.0.0.1 only |
| The prerender job | `collab-prerender` | `shared/prerender`, `shared/data`, `shared/jobs` only | the RPC endpoints, `api.coingecko.com` |
| The content policy step | root, on request | the release's pages and COLLAB's own nginx file | nginx (`nginx -t`, reload) |
| certbot | root | its certificate | Let's Encrypt |

The admin service and the prerender job run sandboxed by systemd: no privileges, a read-only
system apart from their folders, and, for the admin service, no network beyond 127.0.0.1.

### The prerender job's isolation

The prerender job reads untrusted data from the chain and decodes on-chain logos with Pillow. A flaw
there could let it change the prerendered pages, preview images and `/data/` cache, but not the
contracts the site uses, nor the site's code:
- it cannot read `collab.env` (its own settings file holds no secret) or write `config.js`;
- nginx serves no script, style or admin page from its folders, only pages, the `/og/` images and
  member network by their exact names, and `/data/` files ending in `.png`, `.json` or `.txt`;
- nginx follows a link in its folders only when the link and its target have the same owner;
- the pages' policy allows only the site's own scripts, and Google's analytics script
  (`https://www.googletagmanager.com/gtag/`) when an analytics ID is set.

### nginx's limits

- The admin API: 5 requests a second per address, sign-ins 6 a minute, with short bursts; requests
  of at most 256 KB, sign-ins of 4 KB, and 4 KB anywhere else on the site.
- Redirects always go to the main host fixed in the configuration.
- `/admin` is never cached and never indexed, and comes only from the release.
- Hidden files, `config.local.js` and `DEVELOPING.md` are never served.

### The admin API

- It answers only requests that carry the secret header nginx adds.
- A request that changes anything must be JSON, carry `X-Collab-Admin: 1` and come from the main
  host's own pages.
- The token is compared in constant time; 10 failed sign-ins in 10 minutes block an address.
- Records and settings are checked against strict rules, and every write keeps the version it
  replaces.

The [Admin API reference](admin-api.md) documents each check.

### The review of the server kit

The server kit had a review of its own (SV22), AI-assisted like COLLAB's other reviews. It found the
content-policy defences, the `config.js` generation, the admin API and the nginx routing sound, and
it and its check of the fixes raised six hardening items, all fixed with tests:
- SV22-1: folders under `shared/` are created without following a symbolic link a service user could
  plant, and the installer refuses a folder of the wrong owner;
- SV22-2: `/og/` serves only its two image names and the member network;
- SV22-3: signing out survives a restart of the admin service;
- SV22-4: root writes into the services' folders without following links;
- SV22-5: nginx follows a link only when it and its target have the same owner;
- SV22-6: request files are written through random temporary names, never through a planted link.

Each presupposed a foothold as one of the service users, or a captured cookie; none was reachable
from the internet without one. See
[Security](SECURITY.md#release-220-internal-review-of-the-server-kit).

### What the kit does not protect against

- root on the server, or whoever controls its DNS;
- a stolen admin token, until it is replaced;
- an operator who publishes a wrong deployment record: check it with the Check tab.

## Related

- [Security](SECURITY.md)
- [Security at a glance](security-overview.md)
- [The admin page](admin.md)
- [Status, logs and the collab command](monitoring.md)
- [Upgrades, rollbacks and backups](upgrades.md)
