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:
+42
-4
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user