The review document was the last place several findings existed, so it could
not just be deleted. Everything still useful moved out first:
- roadmap items csrf-tokens, template-data-typing, structured-logging and
review-2026-08-followups are now self-contained — each row names the file,
the symptom and what "done" means, instead of pointing at a paragraph number
in a plan file.
- security.md's CSRF accepted-risk bullet now names the csrf-tokens item, so
the two records of that decision cannot drift apart unnoticed; the item says
the same in the other direction.
- CHANGELOG [Unreleased] records the review itself in its intro rather than as
an added file: the document was created and removed inside one unreleased
cycle, so it never was part of a release.
- the declined finding (//go:build linux for the /proc reader) keeps its
rationale in the roadmap so it is not raised again.
Also repaired three links to inbound-relay.md, deleted back when that feature's
plan was cleaned up: the two inbound plans now point at architecture.md § Mail
path for the as-built behaviour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing from the 2026-08-19 code review now lives only in its plan file. Four
new roadmap items, all `candidate`:
- csrf-tokens — §4.4. The review recorded a decision to add per-session tokens
but never turned it into a task, and security.md still argues the header
checks suffice. The item says to settle which of the two stands first.
- template-data-typing — §7.1, map[string]any template payloads.
- structured-logging — §7.2 and §5.5, log/slog plus the per-package logf
aliases.
- review-2026-08-followups — the small remainder: backup re-auth, the missing
DMARC-service / handler / error-page / corrupted-backup tests, and the nine
minor findings (§6.1, §6.2, §6.4, §7.4, §8.6, §9.3, §10.3-10.5) that were
never carried into the phase tables.
§7.3 (build tags for the /proc reader) is deliberately not carried over: it was
declined during the pass, and both documents now say so, so it does not get
re-raised at the next review.
The plan's status table links each open row to its roadmap item, and the
roadmap's recommended-order note points back at the plan. Also unlinked two
CHANGELOG references to plan files deleted after those releases shipped; the
text stays, the dead links do not.
Co-Authored-By: Claude Opus 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>
Close remaining low-priority review follow-ups by removing hardcoded help URLs, improving help drawer accessibility, raising explicit bcrypt cost, and adding validation/injection coverage; also bound postqueue reads with a timeout to avoid hangs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
38 findings across architecture, code quality, security, GUI, tests, documentation, and AGPL compliance. Prioritised 6-phase implementation plan with model recommendations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
No exploitable findings across inbound relay, DMARC ingestion, auto rate
limiting, auth-IP allow-lists, and the new panel surface. Two deliberate
behaviours the pass examined are now recorded as accepted risks:
unauthenticated DMARC report ingest and fail-open auth-IP enforcement.
This closes the note that the inbound-relay path awaited its own review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Plans for domain-stats-auto-ratelimit, dmarc-reports, inbound-relay, panel-docs, queue-retries, and send-log-retention are deleted — all shipped between 1.3.1 and 1.7.0. Only unimplemented plans remain.
Co-authored-by: Cursor <cursoragent@cursor.com>
Found running the new checkPort25ConfigCombinations on selfpost.mixfed.ru: it
compared against raw, unnormalized master.cf text. postconf -F "*/*/chroot=n"
(unconditional, runs after our smtp/inet edits) rewraps the whole file using
master.cf's own line-continuation convention, splitting a single -o value
across lines and re-flowing what were separate -o lines into one paragraph.
Valid Postfix syntax -- postfix check passes -- but plain substring matching
against the raw text broke on it.
parseSMTPInetOptions now joins continuation lines into one token stream and
reconstructs each "-o param=value" assignment before comparing, verified
against a real captured stanza in port25_config_check_test.go. Full make e2e
(13 scenarios plus the two new parser unit tests) is green on the server.
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>
Correct historical gaps: compose cap_add (CAP_FOWNER/FSETID/KILL) in [0.2.0], nav Backup/Sign-out icons in [0.3.0], Export/Danger layout in [1.2.5], and move GHCR per-arch tag cleanup from [1.3.0] to [1.3.1] where the fix landed.
Co-authored-by: Cursor <cursoragent@cursor.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>
Fix port-25 Postfix startup when inbound relay, DMARC ingest, or antispam milter are enabled; consolidate schema-migrations and inbound antispam/quarantine roadmap docs in the changelog.
Co-authored-by: Cursor <cursoragent@cursor.com>
postconf -P cannot set master.cf -o values with whitespace; patch smtp/inet
directly and route DMARC ingest through a dmarc-ingest symlink.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add journal and allow/deny list design, quarantine candidate, roadmap entries, and Composer to Opus to Fable routing note.
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>
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>
Global administrators can set delivery journal retention (7-365 days) in SQLite;
SEND_LOG_RETENTION_DAYS seeds the initial default and the log-tailer re-reads
the value every prune cycle without a restart.
Co-authored-by: Cursor <cursoragent@cursor.com>
Port 25 accepts only configured domains and listed recipients, then forwards to an upstream; the outbound path is unchanged when the flag is off.
Co-authored-by: Cursor <cursoragent@cursor.com>
Close Unreleased for retry-policy panel, persistent queue, and self-contained backups; pin compose and docs to 1.3.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
Numbers come from a one-shot postconf -h at panel start so a manual override is visible after restart, without inventing an attempt count.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move the mail queue under /data so recreate no longer drops deferred mail, and archive data/, compose, .env, and certs/ together for restore on a fresh host.
Co-authored-by: Cursor <cursoragent@cursor.com>
Domains and Inbound keep the add field in the list card. The DMARC candidate hub drills into a domain roll-up and a parsed aggregate report, including a third-party fail. The running panel is unchanged.
Co-Authored-By: Cursor <cursoragent@cursor.com>
Split the hash-sheet prototype into one HTML file per screen, composed from stack, pair, measure, and fill. system.html is the grammar; shell.js is shared chrome. The running panel is unchanged.
Co-Authored-By: Cursor <cursoragent@cursor.com>
Restore Status readings, inbound MX DNS and recipient modes, two-column Backup and Settings, domain-card help, and Host/name Type field height.
Co-Authored-By: Cursor <cursoragent@cursor.com>
Click-through gallery for current and roadmap screens, with a hybrid width proposal and a phone layout for emergency ops. The running panel is unchanged.
Co-Authored-By: Cursor <cursoragent@cursor.com>
imagetools has no rm subcommand; the merge cleanup had been failing silently. Document Gitea-to-GitHub tag mirror pitfalls in development.md. CHANGELOG [1.3.0] updated.
Co-Authored-By: Cursor <cursoragent@cursor.com>
The three post-1.2.5 CI commits that tie image publish to a published GitHub
Release and trim per-arch GHCR tags were missing from the changelog.
Co-authored-by: Cursor <cursoragent@cursor.com>
Run release.yml directly on release:published instead of a dispatcher
workflow. Remove per-arch tags from GHCR after the manifest merge so only
X.Y.Z remains visible to operators.
Co-authored-by: Cursor <cursoragent@cursor.com>
GitHub recorded release published at 16:44:21Z but no release.yml run
started. A thin release-on-publish workflow listens for publish and starts
release.yml via workflow_dispatch (always creates a run). release.yml now
checks out vX.Y.Z from the version input, not main HEAD.
Co-Authored-By: Cursor <cursoragent@cursor.com>
Bare git tag pushes no longer start the image build; publishing a GitHub
Release does. workflow_dispatch still requires an explicit X.Y.Z version.
Docs explain draft vs published releases and that deleting a tag on GitHub
converts a published release back to draft.
Co-Authored-By: Cursor <cursoragent@cursor.com>
Expand the doc-alignment entry to cover guide RBAC and restore Resync
wording; trim the P2 entry of claims superseded by that pass while keeping
its /license route-table addition with a cross-reference.
Co-authored-by: Cursor <cursoragent@cursor.com>
Fix architecture route RBAC, restore Resync, and session/password wording;
correct guide restore/session/PTR claims and README port-587 text; point
the CSRF ADR at authz.go for global route gating.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move restore Resync under Fixed to match e70ba90, and extend the restore
test bullet with the resync boot step and TestResyncAfterRestoreHealsDriftedMaps.
Co-authored-by: Cursor <cursoragent@cursor.com>
Record the operator-guide restructure, merge the stale code-review plan
entry with its completion note, and align the restore Resync bullet with
the current backup package comment.
Co-authored-by: Cursor <cursoragent@cursor.com>
The section explained what a restore means conceptually but gave no
runnable commands. Add a step-by-step in-place restore, a
move-to-a-new-host variant, and the encrypted-backup decrypt-first
sequence, plus the actual version-mismatch error text so operators
know what to expect.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Internal (non-operator) env vars were listed in guide.md's Environment
variables section even though the guide's own text says operators
aren't meant to touch them. They now live in architecture.md §
Configuration, the as-built/internals doc; guide.md keeps a one-line
pointer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Installation now reads Ports, Local trial, Initial setup, Full
deployment (with Fixed image tag nested under it), Environment
variables, Reverse proxy. The step-by-step production deploy and the
per-proxy TLS commands move from README's "Reference deploy" into the
operator guide; README keeps a short pointer instead of duplicating
the walkthrough.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Splits mixed DNS-setup, Operations, Rate-limiting, and Backup sections
along the instance/domain boundary and fixes README anchors that
pointed at the old #dns-setup heading.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>