release: 2.14.4: the follow-ups of the security audit of 2026-10-17
test / test (push) Waiting to run
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:
+21
-7
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user