3 Commits

Author SHA1 Message Date
mix c0d9aa7518 chore/docs: move to GitHub as the single home; drop archived-spec references
Codeberg is being retired as the project's public site, so every reference now
points at GitHub. That includes the Go module path (codeberg.org/mix/selfpost →
github.com/mixeme/selfpost): leaving an import path on a host that is going
away would break `go get` and `go install`, so this is not only a docs change.
Touches go.mod, test/e2e/go.mod, all imports, Makefile MODULE, the -ldflags
version stamp in build/Dockerfile and docs/development.md, the licence headers
in the SVG/HTML assets, and README (no more primary/mirror pair).

Comments no longer cite the archived specification. "spec 7.6.1", "spec 5.1"
and friends pointed into docs/archive/specification-v1.0.md, which is marked as
not a source of truth; each is now a reference to the live document that owns
the subject — architecture.md (with section), product.md, security.md or the
README. The review only asked for the 7.x refs (code-review.md § 4), but 4/5/6/
8/9 had the same defect, so they went too. Comments only, no behaviour change.

Also closes the remaining review items: architecture.md gained a Code layers
section with the layer diagram (A2), and TestParseDelivery gained the exotic
mail.log cases (§ 3).

Fixes a bug that last test found: the delivery-line pattern matched status=
greedily, taking the *last* occurrence on the line. Postfix appends the remote
server's reply verbatim, so a rejection whose reply quoted "status=sent" was
filed as a delivered message in the send log. It now takes the first status=
after the recipient, which is the real field.

R7 (CONTRIBUTING.md) moved to roadmap 2.x — one developer, no external PR flow,
so the file would have no audience yet. R1 (compose image tag) and the git tag
stay in roadmap § v1.x as the release-commit steps.

gofmt/go vet clean on both modules; go test ./... green except the three known
Windows-only failures (file perms, backslash paths, renaming an open file).
Not exercised on the dev server — no Docker locally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 22:14:13 +03:00
mix 670982fb3e docs/chore: Phase 1 doc/code hygiene (code-review.md § Phase 1)
Removes ~30 stale "Phase N" / historical-staging comment references from
code and shell scripts now that v1.0 is done; fixes a stale dashboard
comment claiming applications/send-log were unimplemented; adds a CSRF ADR
to security.md documenting the Origin-check-over-tokens decision; resolves
docs/logo in roadmap.md (directory doesn't exist, criterion already met);
adds a gofmt -l check to CI so unformatted Go fails the build.

The known-limitations write-up for the log-tailer offset gap (the other
Phase 1 item) was already present in architecture.md § Log tailer, so no
change was needed there.

gofmt/go vet/go test clean on both Go modules (main + test/e2e), verified
on the dev server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 16:13:36 +03:00
mix 6ff353b94a panel: security headers and an origin check on unsafe methods
Phase 14.A, both halves of it in one middleware wrapped around the whole
router — outside requireAuth, so POST /login and POST /setup/{token} are
covered too.

Headers: CSP, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and
HSTS only where the deployment is HTTPS-only (the same CookieSecure condition
that gates the cookie's Secure attribute; on the plain-HTTP dev instance HSTS
would pin the browser to a scheme that instance does not speak). HSTS goes
without includeSubDomains on purpose: the panel may sit at an apex, and
forcing HTTPS on every unrelated subdomain of the operator's domain for a year
is not this project's call. They are emitted here rather than delegated to the
reverse proxy, so the part that is easy to get wrong lives in the service.

Origin check: this is what SameSite=Lax cannot do. SameSite is judged per site
(registrable domain), so a neighbouring host — a CMS, a forgotten staging
subdomain — is same-site and its forged POST arrives with the session cookie
attached. Sec-Fetch-Site and Origin are judged per origin and tell it apart.
For the typical deployment, where the panel is a subdomain of a domain used
for other things, that neighbour is the realistic attacker, not a theoretical
one; POST /domains/import is the case that turns a blind write into working
credentials on someone else's relay.

A request carrying neither header is still let through — the risk accepted in
the plan, since a client that old cannot be checked at all and the strict mode
would not protect it, only break the panel in it.

The check compares Origin's host against r.Host, not the full origin: the
panel speaks plain HTTP behind the proxy and does not know its own external
scheme. That makes it depend on the proxy preserving Host. All four shipped
fragments do, but one that rewrites it would turn every form submission into a
403, so the rejection logs both sides of the comparison — otherwise the
symptom reads as "the panel stopped saving anything".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:02:02 +03:00