Review pass over the 1.4.0-1.9.1 work.
Port 25 no longer inherits main.cf's submission milter chain: smtpd_milters
is always overridden on smtp/inet, to the antispam milter or to nothing.
Without that, OpenDKIM (strict, tempfail) gated relayed mail during a signing
outage, and the journal-milter filed inbound mail into the outbound send log
under the sender's domain -- so Deliveries showed other people's mail and a
forged From: consumed that domain's level-2 budget. architecture.md already
described the intended behaviour; the code now matches.
DMARC reports are checked before they are stored: the policy domain must be a
configured sending domain, and must match the +tag of a per-domain hosted
address. The rua address form is predictable, so anyone able to reach it could
file reports under any domain and evict genuine ones through the retention cap.
Zip attachments are now decompressed as well as gzip.
The auto level-2 ceiling is scaled to the level-1 window before the multiplier
is applied; any window other than 3600s shifted the allowance by window/3600.
Migration 0010 indexes send_log(app_login, created_at) -- both the per-MAIL
FROM rate-limit count and the per-application statistics were scanning the
created_at range.
e2e now regenerates the Postfix config in-container for all four port-25 flag
combinations and requires postfix check to pass; the 1.9.1 crash-loop shipped
in 1.4.0 and survived three releases because only the flags-off path was
exercised.
Migration 0009 stays as shipped -- its conversion branch only fires for a
database carrying application-scope allowed_ips from before 1.9.0. The schema
reference notes that the old list was permissive and the new column is
restrictive; the guide documents the allow-list as a sending control on a
fail-open path, not an authentication boundary.
Also: gofmt across cmd/ and internal/, unused duplicate validators dropped from
internal/web/validate, export no longer swallows store errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Application client IP allow-list restricts which addresses may submit as a SASL login; level-2 rate limits override the domain ceiling per application (higher or lower, capped at L1). Migration 0009, authips form, milter enforcement, export/import, and operator docs.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add in-panel Help: CSS-only drawer with seeded Status and domain topics,
«?» entry points on cards, and guide.md boundary for what stays in-repo.
Co-authored-by: Cursor <cursoragent@cursor.com>
Receive DMARC aggregate reports on port 25 and show parsed summaries in the panel. Close Unreleased; pin compose and docs to 1.7.0.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add 30-day send statistics and auto level-2 rate limits on the domain page. Close Unreleased; pin compose and docs to 1.6.0.
Co-authored-by: Cursor <cursoragent@cursor.com>
Close the remaining low-risk items from the full-tree review: rename the settings handler, query assigned domains in SQL, bound the login limiter map, collapse panel.js show/hide helpers, and soften DMARC copy that promised a future in-panel receiver.
Co-authored-by: Cursor <cursoragent@cursor.com>
- .flash.error now renders on the danger surface instead of the success
one; RateLimitErr previously showed as green with red text.
- User delete goes through a confirmation page (GET/POST
/users/{uid}/delete), matching the domain-delete pattern, instead of a
plain submit button next to Save with no confirmation.
- Extracted the repeated DNS Host/Type/Value markup on a domain's page and
the duplicated Settings credentials form into shared partials. No
behaviour change.
docs/plans/code-review.md P3 checked off; CHANGELOG updated.
Co-authored-by: Cursor <cursoragent@cursor.com>
Subgrid Domain settings rows, match Address mode and trusted-IP controls,
and pin Export/Danger buttons to a shared baseline in split cards.
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop the app L1 backstop line, move trusted-IP help under the IP field,
shorten the domain rate-limit title, and align field-pair labels with DMARC.
Co-authored-by: Cursor <cursoragent@cursor.com>
Restore muted L1 backstop line, move domain limit help above Save, and drop
the duplicate Address mode field label.
Co-authored-by: Cursor <cursoragent@cursor.com>
Show the level-1 backstop as its own line and code-row, use st badges for
active/inactive domain and app limits, and fold the polish into 1.2.4.
Co-authored-by: Cursor <cursoragent@cursor.com>
Invert level-2 semantics so domain limits apply to every client IP and
application limits with trusted IPs raise the ceiling above the domain
(still capped by level 1). Panel shows L1, validates maxima, and documents
the model on Settings.
Co-authored-by: Cursor <cursoragent@cursor.com>
Close CHANGELOG [Unreleased] as 1.2.3; bump the compose pin and image references. Includes domain detail layout polish and Domains add-row.
Co-authored-by: Cursor <cursoragent@cursor.com>
Settings moves to /settings with a 308 redirect from /account; domains, Status, Users, and user forms get matching browser titles and h1 text; backup page title reflects domain import.
Co-Authored-By: Composer <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The user form had Back to users at the bottom of the card; all drill-down
pages now use a shared back_link template with a structural test.
Co-Authored-By: Claude <Composer 2.5> <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Lay out view, auth, validate, and handlers under internal/web while keeping
the cmd/panel API unchanged; update roadmap and changelog for web-split closure.
Co-authored-by: Cursor <cursoragent@cursor.com>