The 2026-08-19 full-tree code review's fixes were sitting in Unreleased
alongside in-progress, docs-only panel-redesign prep. Per development.md
§ Roadmap track, docs-only prep gets no version cut until its product
stage ships, so the review-pass entries (all real code/behaviour fixes,
plus migration 0010, the DMARC-zip and port-25 e2e additions) move to a
new [1.9.2] section; the panel-redesign and preflight roadmap items stay
in a fresh Unreleased. Also drops two duplicate CHANGELOG bullets
(bcrypt cost, rate-limiter eviction) that restated an earlier entry
verbatim, and updates every 1.9.1 pin reference (docker-compose image,
README quickstart, guide.md, development.md's planned-cuts table,
schema-migrations.md's migration-0010 row, roadmap.md's review-closure
links) to 1.9.2.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Verified the tree against docs/plans/code-review-2026-08.md and finished the
tasks that were only partly done, plus two defects the pass introduced.
- bcrypt: the setup account was still hashed at DefaultCost (10). The work
factor now has one definition, auth.BcryptCost = 12, used everywhere.
- rate limiter: protecting blocked buckets from eviction was unbounded — an
attacker with many source addresses could grow the map by making each one
fail its way to blocked. Blocked buckets are now evicted past
maxBuckets * 4, nearest expiry first, logged once per window. Tests cover
both halves.
- postqueue: the 5s timeout left the output size unbounded; reads are now
capped at 4 MiB and report truncation.
- spec references: the ~76 `spec N` citations CHANGELOG [0.5.0] claimed were
gone are removed from build/, deploy/, the first two migrations and the
release workflow, with the affected comment paragraphs re-wrapped. Comments
only; shell/YAML/SQL syntax re-checked.
- schema-migrations.md: schema section was headed v9 with the chain at v10,
and three links pointed at deleted plan files.
- internal/configsafe: the shared empty/forbidden-character check behind the
OpenDKIM and Postfix config writers; each writer keeps its own forbidden set.
- dropped the parseDomainRateLimitForm/parseAppRateLimitForm pass-throughs
left behind by the parser merge (only their own tests still called them).
- plan doc now carries a status table: what shipped, what was already in
place, what was declined (//go:build linux for the /proc reader) and the
open CSRF-token question.
Not done, each its own phase: typed template data structs, log/slog, DMARC
service and handler tests, error-page/corrupted-backup tests, backup re-auth.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Port 25, DMARC routes, restore Resync vs reload, image pin, and roadmap
index were stale relative to the running panel and Postfix config.
Co-authored-by: Cursor <cursoragent@cursor.com>
Centralises migration history, 1.x rules, and the 2.x squash gate so
roadmap and architecture no longer carry a stale v5 snapshot.
Co-authored-by: Cursor <cursoragent@cursor.com>