- Two inbound domains on one upstream could never change its TLS mode,
each save refused for the other (medium): SetTransportAs changes the
mode for every domain on the upstream when the one saving may change
them all; a domain user who may not is refused with the mode, not the
other tenant's domain name (security F1: the first version named it).
- A deletion's leftover DKIM key is removed by Resync (Reload
configuration, a restore's first start) for names no domain has.
- The backup's write deadline moves before Check.
- hasUnsavedEdit skips password and file fields and compares a multiple
select's options; htmx.config.timeout 30 s, so a refresh never answered
ends and is tried again; a failed refresh's promise is handled.
- An empty filtered Outbound log says no message matches the filters.
- The dummy bcrypt hash is made at start; a DMARC part over 8 MiB is
refused for its size like the other two limits.
- Health's Configuration box says what Reload writes (design first,
32dfe4a).
- Docs: CHANGELOG, guide § Inbound, security.md (the shared upstream's
mode, the profile form's "username taken"), architecture.md (owed to an
address), the plan's notes corrected.
Tests: the shared upstream (service, handler), the leftover key, all eight
Health boxes ordered, the subject from before migration 0011, the new JS
cases.
Tested on the test server: go vet + go test of 25 packages, make e2e (all
pass, 636 s) on this tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -401,7 +401,10 @@ is sent; the operator's view is guide § Notifications.
|
||||
who are still recipients. A user who comes to hear of a subject only while
|
||||
it is open is not mailed about it, and its ending goes to those who were
|
||||
told. A subject opened before `2.5.4` has no such record, and its ending
|
||||
goes to every recipient, as it did.
|
||||
goes to every recipient, as it did. It is owed to an address: a user who
|
||||
changes their e-mail while a subject is open is a new address, told
|
||||
neither its follow-up nor its end - accepted, a subject lasts hours to
|
||||
days.
|
||||
- **The notifier's own mail is not a subject.** A notification waiting for a
|
||||
mailbox that does not answer, or its bounce coming back to
|
||||
`postmaster@<hostname>` - which receives no mail - is left out of the
|
||||
|
||||
@@ -1200,7 +1200,8 @@ command whose leftover is the owner's alone.
|
||||
would have been cut at 60 s, so `HandleBackup` moves its write deadline
|
||||
to two hours through `http.ResponseController` (no wrapper of the
|
||||
ResponseWriter in the chain; e2e checks the log has no failure to move
|
||||
it). A test pins the four values.
|
||||
it). A test checks the four are set and long enough for a form and a
|
||||
DNS page.
|
||||
- [x] **Polling recovery**: schedule from `htmx:afterRequest` on the box (or pass
|
||||
`source`), handle `sendError`/`timeout` with back-off; `RequireAuth`
|
||||
answers an `HX-Request` with `HX-Redirect: /login` instead of a 303. A
|
||||
@@ -1261,7 +1262,9 @@ command whose leftover is the owner's alone.
|
||||
poll, the pages, the rows' links and the message's way back;
|
||||
`SendLogFilter.Status`, `Since`, `Until` (whole UTC days, as the
|
||||
Inbound log's). A status not in the list is no narrowing. Plans: a
|
||||
domain user's filtered page still goes by `idx_send_log_domain_id`; the
|
||||
domain user's filtered page goes by `idx_send_log_domain_id` (with a day
|
||||
filter by `idx_send_log_domain_id_created_at` and a sort - not pinned by
|
||||
the plan test); the
|
||||
global role's status-only count walks the table (a page view or a 5 s
|
||||
poll while the log is open on a status - tens of thousands of rows at
|
||||
the retention limit, milliseconds), accepted.
|
||||
@@ -1388,6 +1391,31 @@ command whose leftover is the owner's alone.
|
||||
saved before keep the first by domain name, as Postfix did). Tests in
|
||||
inbound and postfix (`TestRenderInboundMapsOneTLSLinePerNextHop`).
|
||||
|
||||
*Changed by the reviews of stage 4 (2026-10-15; technical and security):*
|
||||
**two domains on one upstream could never change its TLS mode** - each
|
||||
save was refused for the other (medium); `inbound.Service.SetTransportAs`
|
||||
now changes the mode for every domain on the upstream when the one saving
|
||||
may change them all, and refuses a domain user who may not with the mode,
|
||||
not the other tenant's name (the security review's F1: the first version's
|
||||
refusal named it). The deletion's leftover key is removed by Resync (Reload
|
||||
configuration, a restore's start) for names no domain has. The backup's
|
||||
write deadline moves before Check. `hasUnsavedEdit` skips password and file
|
||||
fields (a password manager filling the export password made Recalculate
|
||||
ask) and compares a multiple select's options. `htmx.config.timeout` 30 s,
|
||||
so a refresh that is never answered ends and is tried again; the rejected
|
||||
promise of a failed refresh is handled. A filtered empty Outbound log says
|
||||
so. The dummy hash is made at start. A DMARC part over 8 MiB is refused for
|
||||
its size like the others. The Configuration box on Health says what Reload
|
||||
writes (`design:` first). Tests: the shared upstream (service and handler),
|
||||
the leftover key, all eight Health boxes ordered, the legacy subject.
|
||||
Accepted, with the reason: a notification is owed to an address
|
||||
(architecture.md § Notifications); SERVFAIL from every resolver is Unknown
|
||||
- it is not an answer about the record; Reload repeats the restore's list
|
||||
of steps by hand (`HandleReload` and `mailStack.Resync`, both commented); a
|
||||
failed `SetNotifyAddressees` is logged and the subject stays said, the next
|
||||
follow-up finds nobody owed; the profile form's "username taken" is
|
||||
documented in security.md.
|
||||
|
||||
### Stage 5 — image and Postfix hardening (M17–M19, M21, L30–L32) → `2.6.0`
|
||||
|
||||
- [ ] **Supervisor socket**: a group of its own (`supervisor`), panel only;
|
||||
|
||||
+4
-3
@@ -1016,9 +1016,10 @@ reports](#dmarc-reports).) Outbound 465/587 is unchanged.
|
||||
**Panel** (`/inbound/domains`): the global role adds and deletes domains. On a
|
||||
domain's page, set the upstream host/port and TLS to that hop (opportunistic /
|
||||
required / off), and choose recipients. An upstream has one TLS mode: Postfix
|
||||
keeps the TLS policy per next hop, not per domain, so a second domain sent to
|
||||
the same host and port with another mode is refused, naming the domain that
|
||||
already uses it. Recipients are an allow-list, or any address at that
|
||||
keeps the TLS policy per next hop, not per domain, so the mode saved for one
|
||||
domain is the mode of every domain forwarded to the same host and port, and
|
||||
they are changed with it; a *domain* user who does not manage all of them is
|
||||
refused, and told the mode in use. Recipients are an allow-list, or any address at that
|
||||
domain. Inbound domains are assigned to a user with the *domain* role
|
||||
separately from outbound ones (see [Users](#users)); such a user sees and
|
||||
changes the upstream and recipients of the inbound domains assigned to them,
|
||||
|
||||
+9
-2
@@ -610,7 +610,12 @@ deferred item from the roadmap.
|
||||
panel itself makes no request to the upstream. Trigger to revisit: `domain`
|
||||
users who are not trusted operators — the same condition as for CSRF tokens
|
||||
— at which point an allow-list of upstream networks, or restricting the
|
||||
field to the global role, is the fix.
|
||||
field to the global role, is the fix. Since `2.5.4` an upstream has one TLS
|
||||
mode for every inbound domain forwarded to it (Postfix keys the policy by
|
||||
the next hop): a `domain` user saving one that another tenant's domain
|
||||
uses with another mode is refused and told that mode - not the domain -
|
||||
so the form tells whether some domain forwards to a given host and port.
|
||||
Accepted with the rest of this item.
|
||||
- **Account's profile (username, e-mail) is saved without the password.**
|
||||
Saving the profile does not ask for the current password, as changing the
|
||||
password does. Since 2.3.0 the e-mail is where notifications go, so a stolen
|
||||
@@ -619,7 +624,9 @@ deferred item from the roadmap.
|
||||
certificate expires, sender addresses of old queued mail - and the user
|
||||
stops hearing it. Nothing in a notification gives access. A `domain` user
|
||||
can likewise point signed mail of this server at a third party's address,
|
||||
a dozen messages a day per assigned domain at most. It
|
||||
a dozen messages a day per assigned domain at most. The profile form also
|
||||
says when a username is taken, so a signed-in user can tell which
|
||||
usernames exist; signing in does not (`2.5.4`). It
|
||||
becomes a requirement to ask for the current password when changing
|
||||
`users.email` as soon as [password-reset](roadmap.md#password-reset) mails a
|
||||
link to it — from then on whoever can change the address could take the
|
||||
|
||||
Reference in New Issue
Block a user