release: 0.5.0: Pre-1.0 security review, domain-admin role, doc consolidation
A bounce could be recorded as a successful delivery. The log-tailer's Fixed: - A bounce could be recorded as a successful delivery. The log-tailer's - Log-tailer resumes where it stopped instead of jumping to end-of-file on - Level-2 rate limit no longer overshoots under concurrency: messages that Changed: - The project has a single public home: `github.com/mixeme/selfpost`. Codeberg - Code comments no longer cite the archived specification. References like - `docs/code-review.md` is gone. Its plan is finished — phases 0 (bar the - `docs/architecture.md` gained a *Code layers* section: a diagram of - Phase 1 doc/code hygiene (`docs/code-review.md`): removed ~30 stale … (1 more in CHANGELOG) Security: - Pre-release security review (plan § D, model Fable, 2026-08-06): full pass - `saslpasswd2` argv: the application login is now passed after a `--` Added: - Optional password encryption for the two secret-bearing downloads (plan - Domain import accepts an encrypted export: the envelope is detected by its - `selfpost-backup` writes encrypted archives and reads them back: - `TestParseDelivery` covers the exotic mail.log shapes the review asked for - docs: README *Encrypting a backup or export*; `docs/security.md` § … (14 more in CHANGELOG) Removed: - docs: `security.md` — accepted risk «restore old backup revives session rows» Changed: - docs: `implementation-plan.md` trimmed to the sole open v1.x gate — pre-release - docs: `architecture.md` — sessions (SQLite, idle renew, password change), - docs: `development.md` — expanded e2e stack and `release.yml` matrix workflow. - docs: `security.md` — accepted risk for send-log rows stuck at `queued` after - docs: `roadmap.md` — optional send-log / `mail.log` follow-ups under v1.x tail. … (10 more in CHANGELOG) Full detail: CHANGELOG.md (0.5.0).
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
module codeberg.org/mix/selfpost/test/e2e
|
||||
module github.com/mixeme/selfpost/test/e2e
|
||||
|
||||
go 1.26
|
||||
|
||||
|
||||
+12
-12
@@ -68,8 +68,8 @@ func testLevel2RateLimit(t *testing.T, sc *scenario) {
|
||||
}
|
||||
|
||||
// testSenderLoginMismatch is plan C.4 negative check 2: an authenticated
|
||||
// application cannot send as a sender it does not own (spec 5.1 p.3,
|
||||
// reject_sender_login_mismatch) — the core anti-spoofing control.
|
||||
// application cannot send as a sender it does not own (architecture.md § Mail
|
||||
// path, reject_sender_login_mismatch) — the core anti-spoofing control.
|
||||
func testSenderLoginMismatch(t *testing.T, sc *scenario) {
|
||||
res := attemptSend(sendAttempt{
|
||||
authLogin: sc.appLogin, authPassword: sc.appPassword,
|
||||
@@ -118,10 +118,10 @@ func testForeignRelayRejected(t *testing.T, sc *scenario) {
|
||||
}
|
||||
|
||||
// testJournalMilterFailOpen is plan C.4 negative check 6: the journal-milter
|
||||
// is monitoring-only and fails open (spec 7.3) — stopping the panel process
|
||||
// (which owns the milter socket) must not block mail, and must not crash the
|
||||
// container (crashexit only fires on PROCESS_STATE_FATAL, a clean supervisor
|
||||
// stop is STOPPED, see build/crashexit.py).
|
||||
// is monitoring-only and fails open (architecture.md § Mail path) — stopping
|
||||
// the panel process (which owns the milter socket) must not block mail, and
|
||||
// must not crash the container (crashexit only fires on PROCESS_STATE_FATAL, a
|
||||
// clean supervisor stop is STOPPED, see build/crashexit.py).
|
||||
func testJournalMilterFailOpen(t *testing.T, sc *scenario) {
|
||||
if _, err := h.execIn("selfpost", "supervisorctl", "-c", "/etc/supervisor/supervisord.conf", "stop", "panel"); err != nil {
|
||||
t.Fatalf("stop panel: %v", err)
|
||||
@@ -196,12 +196,12 @@ func testSessionSurvivesRestart(t *testing.T, sc *scenario) {
|
||||
}
|
||||
|
||||
// testLevel1RateLimit is plan C.4 negative check 4: the native Postfix anvil
|
||||
// backstop (smtpd_client_message_rate_limit, spec 5 p.5), set by the override
|
||||
// to RATE_LIMIT_MESSAGES_PER_IP=50, rejects once exceeded. It uses a dedicated
|
||||
// application with no level-2 limit of its own, and retries well past that
|
||||
// count, so the result is unambiguous regardless of how much of the shared
|
||||
// per-IP budget earlier subtests already spent (they stay well under 50
|
||||
// between them).
|
||||
// backstop (smtpd_client_message_rate_limit, README § Rate limiting), set by
|
||||
// the override to RATE_LIMIT_MESSAGES_PER_IP=50, rejects once exceeded. It
|
||||
// uses a dedicated application with no level-2 limit of its own, and retries
|
||||
// well past that count, so the result is unambiguous regardless of how much of
|
||||
// the shared per-IP budget earlier subtests already spent (they stay well
|
||||
// under 50 between them).
|
||||
func testLevel1RateLimit(t *testing.T, sc *scenario) {
|
||||
login, password, err := sc.panel.addApplication(sc.domainID, "l1app", "wildcard", "")
|
||||
if err != nil {
|
||||
|
||||
@@ -145,7 +145,7 @@ func (c *panelClient) dkimRecord(domainID string) (name, value string, err error
|
||||
}
|
||||
|
||||
// addApplication submits the add-application form and returns the one-shot
|
||||
// login/password the panel renders inline (spec 7.6.1 — never recoverable
|
||||
// login/password the panel renders inline (security.md — never recoverable
|
||||
// later, so this is the only place to read it).
|
||||
func (c *panelClient) addApplication(domainID, login, mode, addresses string) (appLogin, password string, err error) {
|
||||
resp, body, err := c.postForm("/domains/"+domainID+"/applications", url.Values{
|
||||
@@ -170,8 +170,9 @@ func (c *panelClient) addApplication(domainID, login, mode, addresses string) (a
|
||||
return appLogin, password, nil
|
||||
}
|
||||
|
||||
// setRateLimit saves a level-2 differentiated limit (spec 7.4) on either a
|
||||
// domain (/domains/{id}/ratelimit) or an application (/applications/{id}/ratelimit).
|
||||
// setRateLimit saves a level-2 differentiated limit (README § Rate limiting)
|
||||
// on either a domain (/domains/{id}/ratelimit) or an application
|
||||
// (/applications/{id}/ratelimit).
|
||||
func (c *panelClient) setRateLimit(path, allowedIP string, maxMessages, windowSeconds int) error {
|
||||
resp, body, err := c.postForm(path, url.Values{
|
||||
"allowed_ips": {allowedIP},
|
||||
|
||||
Reference in New Issue
Block a user