release: 1.9.2: Review pass: port-25 milters, DMARC validation, rate-limit fix

Review pass over `1.4.0`…`1.9.1`: port 25 no longer shares the submission milter chain, DMARC reports are validated before they are stored, and the auto rate limit is correct for a non-default level-1 window. Schema migration `0010` (index only). Upgrading is a tag bump.
Added:
  - SQLite migration `0010_send_log_app_login_index.sql` —
  - DMARC aggregate reports are accepted as **zip** as well as gzip or plain XML
  - e2e: `checkPort25ConfigCombinations` regenerates the Postfix configuration
Changed:
  - README hero line and a **Positioning** block in `docs/product.md` record the
Fixed:
  - The setup account's password is hashed at bcrypt cost `12` like every other
  - Login/setup rate limiter: protecting blocked buckets from eviction is now
  - `postqueue -p` output is bounded to 4 MiB (the timeout alone left the size of
  - [schema-migrations.md](docs/schema-migrations.md): the schema section was
  - postqueue reader is now time-bounded: `postqueue -p` runs under a 5-second
  … (11 more in CHANGELOG)
Changed:
  - [roadmap.md](docs/roadmap.md) now carries everything the 2026-08-19 code
  - Extracted `internal/configsafe` — the empty-value and forbidden-character
  - Build, deploy and migration comments no longer cite the archived
  - Dropped the `parseDomainRateLimitForm` / `parseAppRateLimitForm` pass-through
  - Extracted shared helpers: `internal/atomicfile` (atomic temp-write+rename) and
  … (7 more in CHANGELOG)
Removed:
  - Unused legacy asset `docs/assets/icon.png` (superseded by the four brand SVGs
  - Plan documents for already-shipped features: `domain-stats-auto-ratelimit`,

Full detail: CHANGELOG.md (1.9.2).
This commit is contained in:
mix
2026-09-22 15:24:53 +03:00
parent 967d3d95de
commit 7ef7f75246
179 changed files with 11558 additions and 1738 deletions
+42 -4
View File
@@ -10,9 +10,14 @@ login in the `saslpasswd2` argv
2026-08-13 full-tree review against this document — send-log authorization for
domain administrators, the atomic level-2 admit (`tryAdmit`), fail-closed
session creation, and application-delete ordering: no findings, and nothing
needed adding to the accepted risks. The inbound-relay path (port 25, maps,
panel) is described under Mail path below and is **not yet** in that review
history — it awaits a separate Fable pass before 1.4.0. (2) **Accepted risks** —
needed adding to the accepted risks. The 2026-08-19 review (Fable) covered the
whole diff from 1.3.0 (`7052395`) to HEAD past 1.9.1 — the inbound-relay path
(port 25, maps, panel), DMARC aggregate-report ingestion, automatic rate
limiting, application auth-IP allow-lists, and the new panel surface
(handlers, templates, settings): no exploitable findings; two deliberate
behaviours the pass examined are now recorded under Accepted risks
(unauthenticated DMARC ingest, fail-open auth-IP enforcement).
(2) **Accepted risks** —
deliberate departures beyond the mandatory, recorded so the decision is not
lost.
@@ -143,7 +148,10 @@ deferred item from the roadmap.
(see the ADR below). A token would not save the panel from XSS inside it
either: code executing in the panel's origin sends the request itself —
against that, `html/template` auto-escaping and CSP do the work, which is why
templates must contain no inline scripts and no inline styles.
templates must contain no inline scripts and no inline styles. Tracked as
[csrf-tokens](roadmap.md#csrf-tokens): implementing that item replaces this
bullet and the ADR below, and dropping it leaves them as the single record of
the decision. The two must not disagree.
- **Destructive-action confirmation (`data-confirm`) is JavaScript-only.**
Delete, regenerate-password, and clear-rate-limit forms carry a
`data-confirm` prompt handled entirely in
@@ -178,6 +186,36 @@ deferred item from the roadmap.
until the tailer has read the log to the end, and touches nothing if
`postqueue` is unreadable. See [architecture.md](architecture.md) § Log
tailer.
- **DMARC aggregate reports are accepted without authentication.** Anyone on
the internet can mail a fabricated report (or displace a genuine one — the
`UNIQUE(reporter, report_id, domain)` upsert is delete-then-insert) and skew
the alignment statistics the panel shows. Inherent to how `rua` works:
reporters are arbitrary third parties and the mailbox address is published in
DNS. The ingest already constrains the meaningful part — a report cannot be
filed under a domain this instance does not host, and the claimed domain is
cross-checked against the `+tag` of the delivery address — and the data is
advisory statistics only; nothing enforces off it. The trigger to revisit is
any feature that lets report contents drive an automatic action (policy
changes, rate limiting, alerting an operator into a config change).
- **The application auth-IP allow-list is enforced fail-open.** The check runs
in the journal milter (`internal/milter/authip.go`) with
`default_action=accept` and skips on store errors, so if the milter is down
the restriction is not applied; the SASL login itself always succeeds. This
is documented in [guide.md](guide.md) as **not an authentication boundary** —
it is a blast-radius limiter for a leaked application password, on the same
fail-open path as level-2 rate limiting, and a milter outage already stops
the journal from recording sends at all. The trigger to revisit is a
requirement for the allow-list to hold as a security boundary — at which
point it must move to the SASL/smtpd layer and fail closed.
- **Level-2 in-flight reservations can outlive a dropped SMTP session until TTL.**
The journal milter cannot observe connection-close directly through
go-milter, so if a client disconnects without an explicit `Abort`, its
in-memory reservation is released by TTL expiry, not immediately. The map is
bounded by key and TTL (currently 10 minutes), so the effect is temporary
over-counting on that app/domain key under churn. Accepted deliberately:
immediate cleanup would require protocol hooks the current milter interface
does not expose; the TTL bound keeps it finite. Trigger to revisit: sustained
high-volume false throttling attributable to orphaned reservations.
- **Access to `mail.log` from the unprivileged panel.** The `/data/log`
directory is `2750 postfix:selfpost` and the file is `0640`: `postlogd` (user
`postfix`) writes, the panel reads through the shared `selfpost` group, and