diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4b162..cc4b501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version ## [Unreleased] +## [2.14.4] - 2026-10-17 + +The follow-ups of the security audit of 2026-10-17 +([docs/security-audit-2026-10-17.md](docs/security-audit-2026-10-17.md)), +stages 0-6: its one high (A2), its seven mediums and the lows. No migration; +schema 13. Read *Upgrading* below: the TLS key's group, and copy +`antispam/` again where the sidecar runs. + ### Upgrading - **Give the TLS private key to group 10997** (audit I1): `chgrp 10997 @@ -12,9 +20,22 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version same in whatever renews it. Group 10998, which the guide named until now, has Postfix in it; the backup keeps working with either, but only 10997 keeps the key from Postfix's daemons. +- **With the antispam sidecar, copy `antispam/` again** (audit S4): rspamd + 4.2.2, and a third file, `antispam/rspamd/worker-proxy.inc`, that the + fragment now mounts; `docker compose ... up -d` recreates the sidecar. +- **Behind Apache on the host, set `TRUSTED_PROXY_CIDR`** if it is not set + (audit A1): the guide's install step prints the gateway to put there; the + panel's log says so after the first requests when it is missing. ### Security +- **A stolen `global` session no longer takes a full backup** (security + audit 2026-10-17, A2, high): the user form set the signed-in user's own + password without the current one, which then passed the backup's password + check. Adding a user, setting a password, changing an e-mail or a role on + the user form, and deleting a user now ask for your own account password, + as a backup does, under the same limit on wrong guesses - which now + counts wrong passwords only. - **One stranger behind a shared proxy no longer locks everyone out of signing in** (audit A1, A8, A9): sign-in counts failures per address and username (a hundred per address alone), a right password no longer @@ -65,13 +86,6 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version the internet included - could read it. A new group, `backupkey` (10997), holds the panel alone; the guide, Help and the backup's refusal name it. -- **A stolen `global` session no longer takes a full backup** (security - audit 2026-10-17, A2, high): the user form set the signed-in user's own - password without the current one, which then passed the backup's password - check. Adding a user, setting a password, changing an e-mail or a role on - the user form, and deleting a user now ask for your own account password, - as a backup does, under the same limit on wrong guesses - which now - counts wrong passwords only. - **A domain's rate limit is taken only by its own applications** (audit M1): the level-2 domain limit was read from `MAIL FROM`, which reaches the milter before Postfix holds it to the login, so a client that never diff --git a/README.md b/README.md index a51329b..1d6ddae 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,8 @@ outbound mail will not reach the real internet without DNS, PTR, and port 25. > local image and pulls nothing: > > ```sh -> docker build -f build/Dockerfile --build-arg VERSION=2.14.3 \ -> -t ghcr.io/mixeme/selfpost:2.14.3 . +> docker build -f build/Dockerfile --build-arg VERSION=2.14.4 \ +> -t ghcr.io/mixeme/selfpost:2.14.4 . > ``` ```sh @@ -122,7 +122,7 @@ docker run --rm -d --name selfpost-try \ -e SELFPOST_HOSTNAME=mail.local.test \ -e PANEL_COOKIE_SECURE=false \ -v selfpost-try-data:/data \ - ghcr.io/mixeme/selfpost:2.14.3 + ghcr.io/mixeme/selfpost:2.14.4 ``` This trial runs with Docker's default capabilities and no diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 279259f..e9eb1bd 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -23,7 +23,7 @@ services: selfpost: - image: ghcr.io/mixeme/selfpost:2.14.3 + image: ghcr.io/mixeme/selfpost:2.14.4 restart: unless-stopped environment: SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}" diff --git a/docs/development.md b/docs/development.md index 764a65c..476b600 100644 --- a/docs/development.md +++ b/docs/development.md @@ -107,7 +107,7 @@ than the default «meaningful step» rule in [§ Commits and release build](#com 2. Git tag `vX.Y.Z` and publish the GitHub Release only on explicit request (see [§ Release image](#release-image)). -**Planned version cuts** (from pin `2.14.3`; adjust if semver changes mid-track): +**Planned version cuts** (from pin `2.14.4`; adjust if semver changes mid-track): | Stage | ID | Cut | |---|---|---| @@ -155,6 +155,7 @@ than the default «meaningful step» rule in [§ Commits and release build](#com | 42 | overview-head | `2.14.1` | | 43 | dmarc-day, health-sockets | `2.14.2` | | 44 | label-tag | `2.14.3` | +| 45 | security-audit-2026-10-17, stages 0-6 | `2.14.4` | Docs-only prep (checklists in plans, no product code) uses the same per-step commit rule but **no** version cut until the next product stage ships. diff --git a/docs/guide.md b/docs/guide.md index 8bcd673..3d9880d 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -218,14 +218,14 @@ cat ./data/setup-token #### Fixed image tag `deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`), -deliberately never `:latest`. The current pin is `2.14.3`. **No 2.x image is +deliberately never `:latest`. The current pin is `2.14.4`. **No 2.x image is published on ghcr.io yet** (the newest there is `1.3.0`); until one is, build it from the checkout under the pinned name, and Compose uses the local image instead of pulling: ```sh -docker build -f build/Dockerfile --build-arg VERSION=2.14.3 \ - -t ghcr.io/mixeme/selfpost:2.14.3 . +docker build -f build/Dockerfile --build-arg VERSION=2.14.4 \ + -t ghcr.io/mixeme/selfpost:2.14.4 . ``` `VERSION` must equal the tag: it is the version the backup check compares @@ -1067,7 +1067,7 @@ A version mismatch at step 5 refuses to start and leaves `/data` untouched — the panel exits with a message naming the tag to use, e.g.: ``` -backup: this backup was created by SelfPost 2.14.2 but this image is 2.14.3 — restore into the matching image (selfpost:2.14.2) +backup: this backup was created by SelfPost 2.14.3 but this image is 2.14.4 — restore into the matching image (selfpost:2.14.3) ``` Fix the tag in `docker-compose.yml` (build that tag as under [Fixed image diff --git a/docs/roadmap.md b/docs/roadmap.md index b693c68..cce5652 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -28,7 +28,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md). | ID | Topic | Status | Progress | Plan | |---|---|---|---|---| -| security-audit-2026-10-17 | Follow-ups of the 2026-10-17 security audit: A2 (high), seven mediums, the lows | stages 0-6 built 2026-10-17 for `2.14.4`; stage H (the host, S1-S3, S5) the owner's | 27/29 | [security-audit-2026-10-17.md](security-audit-2026-10-17.md) | +| security-audit-2026-10-17 | Follow-ups of the 2026-10-17 security audit: A2 (high), seven mediums, the lows | stages 0-6 shipped `2.14.4` (2026-10-17); stage H (the host, S1-S3, S5) the owner's | 27/29 | [security-audit-2026-10-17.md](security-audit-2026-10-17.md) | | upstream-tls-verify | Inbound: offer "Verified" TLS to the upstream (Postfix `secure`, the upstream's name checked), beside Required, which checks no certificate (audit M7) | proposed | — | — | | server-dmarc-hint | Server › Settings lists a DMARC record for the server's own name beside its DKIM and SPF (audit S3, product side) | proposed | — | — | | inbound-upstream-result | Inbound › Log shows what the upstream answered: sent, deferred, bounced, with its last reply | shipped `2.10.0` | 7/7 | [plans/inbound-upstream-result.md](plans/inbound-upstream-result.md) | @@ -68,7 +68,7 @@ stages 1 and 2 as `2.5.0` the same day, stage 2b as `2.5.1`, stage 2c as `2.6.0` the same day; stage 6 (documents and tests, no cut) closed the audit. Of the three rows from [own-mta-builds](#own-mta-builds), **port25-bare-newline** shipped as `2.6.1` the same day and -**base-trixie** as `2.7.0`, and **panel-dkim-signing** as `2.8.0` (2026-10-16). **dns-record-collapse**, **settings-save-row** and **notification-mail-recheck** shipped together as `2.9.0` the same day, with `/data/opendkim` removed as panel-dkim-signing planned, then `2.9.1` (log panes on a phone) and **inbound-upstream-result** as `2.10.0`. What is agreed after it is marked so in the index; every other +**base-trixie** as `2.7.0`, and **panel-dkim-signing** as `2.8.0` (2026-10-16). **dns-record-collapse**, **settings-save-row** and **notification-mail-recheck** shipped together as `2.9.0` the same day, with `/data/opendkim` removed as panel-dkim-signing planned, then `2.9.1` (log panes on a phone) and **inbound-upstream-result** as `2.10.0`. The follow-ups of the [security audit of 2026-10-17](security-audit-2026-10-17.md) shipped together as `2.14.4`, stages 0-6 in one cut; its stage H is the owner's host. What is agreed after it is marked so in the index; every other remaining item is a candidate. panel-docs shipped in [CHANGELOG.md](../CHANGELOG.md) `[1.8.0]`; dmarc-reports in `[1.7.0]` (its ingest path first ran in `2.0.0` and was reviewed on 2026-10-11, see diff --git a/docs/security-audit-2026-10-17.md b/docs/security-audit-2026-10-17.md index 94ad79f..97b08db 100644 --- a/docs/security-audit-2026-10-17.md +++ b/docs/security-audit-2026-10-17.md @@ -683,7 +683,7 @@ the argument. "letsencrypt.org"`. Done when `dig +short TXT _dmarc.selfpost.mixfed.ru` answers. -### Stage 0 — the high and the cheap guards (~5 h) → patch release +### Stage 0 — the high and the cheap guards (~5 h) → patch release (shipped `2.14.4`, 2026-10-17) - [x] **A2.** `reauth` on user create, on a user update that carries `password` or changes `role`, and on user delete; the user form gets the @@ -717,7 +717,7 @@ the argument. - [x] **X1.** SECURITY.md's table: `latest 2.x` supported, `1.x` not. — **Sonnet**, 0.25 h. *Done 2026-10-17.* -### Stage 1 — the tenant boundary in the mail path (~7 h) → patch release +### Stage 1 — the tenant boundary in the mail path (~7 h) → patch release (shipped `2.14.4`, 2026-10-17) - [x] **M1.** `overLimit` and `recordRejected` take the domain from the login (`ApplicationByLogin`), return early on an empty login; the reader's two @@ -753,7 +753,7 @@ the argument. - [x] **W3.** A mutex around `Resync`'s read + write + reload. — **Sonnet**, 0.5 h. *Done 2026-10-17:* TestResyncRunsOneAtATime. -### Stage 2 — the key group (~2.5 h) → patch release, then the test server's `certs/` +### Stage 2 — the key group (~2.5 h) → patch release, then the test server's `certs/` (shipped `2.14.4`, 2026-10-17) - [x] **I1.** `groupadd --system --gid 10997 backupkey; usermod -aG backupkey panel` in the Dockerfile; the guide's two `chgrp 10998` become `10997` @@ -767,7 +767,7 @@ the argument. is root and `testFullBackupRestore` checks both uids; the test server's key at the rollout. -### Stage 3 — limits and defaults (~7 h) +### Stage 3 — limits and defaults (~7 h) (shipped `2.14.4`, 2026-10-17) - [x] **A1.** (a) Compose and guide set `TRUSTED_PROXY_CIDR` for the Apache-on-host scenario (the Docker gateway, with the command that prints @@ -797,7 +797,7 @@ the argument. private address - a trusted proxy's peer would have let the internet in again through that proxy; TestHealthzAnswersOnlyLocalProbes. -### Stage 4 — resources and imports (~5 h) +### Stage 4 — resources and imports (~5 h) (shipped `2.14.4`, 2026-10-17) - [x] **I3.** `pids_limit: 512` in every compose fragment; the comment corrected; one e2e run. — **Sonnet**, 0.5 h. *Done 2026-10-17:* 1024 for @@ -832,7 +832,7 @@ the argument. openssl's HMAC, the deny list's 554 message, healthy. The live sidecar at the rollout. -### Stage 5 — supply chain and currency (~2.5 h) +### Stage 5 — supply chain and currency (~2.5 h) (shipped `2.14.4`, 2026-10-17) - [x] **I4.** Actions pinned by SHA with the version in a comment; `govulncheck ./...` in `test.yml` and in `release.yml` before the push (a @@ -855,7 +855,7 @@ the argument. test server both ways. 1.5 h. -### Stage 6 — the rest, and what to accept (~3 h) +### Stage 6 — the rest, and what to accept (~3 h) (shipped `2.14.4`, 2026-10-17) - [x] **M7.** guide: upstream `encrypt` checks no certificate. — **Sonnet**, 0.25 h. *Done 2026-10-17:* guide and Help. (Offering `secure` is hours and a `domain`-user-facing choice;