release: 2.14.4: the follow-ups of the security audit of 2026-10-17
test / test (push) Waiting to run

Stages 0-6 of docs/security-audit-2026-10-17.md in one cut: the high A2
(the user form asks for the account password), the seven mediums (M1, M2,
M3, W1, I1, I2, A1) and the lows and nits the plan names, with three
read-only security reviews of the fixes and their findings fixed. Stage H
(the test server's host) is the owner's.

No migration; schema 13. Upgrading: give the TLS key to group 10997;
copy antispam/ again where the sidecar runs (rspamd 4.2.2, a third file);
set TRUSTED_PROXY_CIDR behind Apache on the host if unset.

make e2e (64 passed) and go vet + go test (26 packages) on the test server
for 32f6ed6, the tree this release commit adds only pins and notes to. The
pins (compose, README, guide), the guide's backup example, the planned
cuts, the roadmap row and the plan's stages marked shipped. No tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-17 23:12:55 +03:00
co-authored by Claude Opus 5.5
parent 32f6ed626d
commit 58356fb4d4
7 changed files with 40 additions and 25 deletions
+21 -7
View File
@@ -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
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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}"
+2 -1
View File
@@ -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.
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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
+7 -7
View File
@@ -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;