v1.5.1: frontend auth hardening — rate limiting, email verification, honeypots, CSV safety

Security-only release per ADR-0001. Closes all six ship-blockers from the
post-v1.5.0 audit: PSEC-001 (no rate limiting), PSEC-002 (account
enumeration via separate error messages), PSEC-003 (registration auto-login
with no email verification), PSEC-004 (ungated "My Data" templates),
PSEC-005 (CSV formula injection on subscriber export), PSEC-006 (no
honeypot/CAPTCHA on public forms).

Adds inc/config.php (single source of truth for all security thresholds,
filterable via peptide_starter_security_config), inc/rate-limiter.php
(Peptide_Starter_Rate_Limiter + Cloudflare-aware client IP resolver,
transient-backed, never stores raw IPs), and inc/email-verification.php
(43-char token, 24h TTL, /verify rewrite, rate-limited resend endpoint).

Auth handlers rewritten to: unify all failure messages, rate-limit before
any expensive check, drop registrations with a filled honeypot silently,
stop auto-login on register, send a verification email instead. The
require_login() helper lives in inc/helpers.php and is applied at the top
of page-tracker.php, page-subject-log.php, and page-protocol-builder.php;
page-calculator.php stays open (no PII, no writes).

Newsletter signup gains: consent checkbox, privacy link, honeypot,
autoload=false on the wp_options blob, unsubscribe token stored per row,
/newsletter-unsubscribe route, merged success+duplicate into a single "ok"
state. Admin export runs every cell through peptide_starter_csv_safe()
which prefixes leading =/+/-/@/tab/CR with a quote.

Contact handler hardens against email-header injection (rejects name
values with \r \n , < >), rate-limits, honors honeypot, tags mail
failures with a request ID for correlation without logging message
content.

Adds Tools → Mail Test (inc/mail-diagnostic.php): a permanent wp_mail
probe so we can check Hostinger outbound deliverability any time it looks
flaky. Adds a one-time admin_init migration that enrols pre-v1.5.1
subscribers into the verification flow (currently a no-op — zero users —
but guarded for the future).

Other cleanup from the audit: fallback primary menu moved out of
header.php into inc/helpers.php (redeclaration risk), placeholder footer
language links replaced with "Translations coming soon", footer copyright
now reads through the customizer getter, settings-panel gains a proper
Tab/Shift+Tab focus trap and saves/restores body overflow, documentation
heading IDs slugified from heading text with collision counters, JS files
standardized on const/let.

Six new PHPUnit suites cover rate limiter lifecycle, auth handler error
unification and rate limits, email verification token generation and
expiry, contact handler header injection + rate limit + honeypot,
newsletter signup consent + CSV safety, and require_login gate behavior.

ARCHITECTURE.md and CONVENTIONS.md updated in the same commit — new
module layout, data flows, security patterns for rate-limiting,
honeypotting, gating, and CSV-safe export.

Version bumped to 1.5.1 in style.css and functions.php.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
peptiderepo
2026-04-14 13:01:22 +07:00
parent d28d5f45d5
commit cb8b73f4ab
34 changed files with 2816 additions and 745 deletions

View File

@@ -2,6 +2,129 @@
All notable changes to the Peptide Starter Theme are documented in this file.
## [1.5.1] - 2026-04-14 — Security
Security-only release hardening the frontend authentication surface introduced
in v1.5.0. Governed by ADR-0001 (Frontend authentication and abuse-control
strategy). No breaking changes to public routes, AJAX action names, or nonce
action strings.
### Security fixes
- **PSEC-001** — Rate limiting on login (5/min per IP+email), registration
(3/hour per IP), contact (5/hour per IP), newsletter (3/hour per IP), and
verification resend (2/hour per IP+email). Transient-backed, auto-expiring.
Storage never holds raw IPs — keys are truncated `wp_hash` digests.
- **PSEC-002** — Unified error messages on login and registration. Removed
separate "username exists" / "email exists" / "user not found" branches
that enabled account enumeration via the error-message oracle. All login
failures now return `Invalid email or password.`; all registration
validation failures return `Unable to create account. Please check your
entries and try again.`
- **PSEC-003** — Removed auto-login from the registration handler. New
registrations now go through an email verification flow: a 43-char random
token (stored in user meta with a 24h TTL) is emailed via `wp_mail` and
must be clicked to activate the account. Verification route is `/verify`.
`wp_mail` deliverability can be validated any time via the new admin tool
at Tools → Mail Test.
- **PSEC-004** — `peptide_starter_require_login()` gate applied at the top
of `page-subject-log.php`, `page-tracker.php`, `page-protocol-builder.php`.
Unauthenticated users bounce to `/auth?redirect_to=…`; unverified users
bounce to `/profile?verify_required=1` which surfaces a resend button.
`page-calculator.php` stays open — it holds no PII and does no writes.
- **PSEC-005** — CSV injection fix in the newsletter admin export. Every
cell passed to `fputcsv` routes through `peptide_starter_csv_safe()`,
which prefixes values starting with `=`, `+`, `-`, `@`, `\t`, `\r` with
a single quote so spreadsheet apps won't interpret them as formulas.
- **PSEC-006** — Honeypot fields on all four public forms (login, register,
contact, newsletter). Any non-empty value fakes a success response and
drops the submission. No PII is ever logged — only a sha256-truncated
IP hash so admins can correlate if they need to.
### Added
- `inc/config.php` — single source of truth for every security threshold;
filterable via `peptide_starter_security_config`.
- `inc/rate-limiter.php` — `Peptide_Starter_Rate_Limiter` class with
`check`/`record`/`reset` lifecycle + `peptide_starter_get_client_ip()`
helper (Cloudflare-aware).
- `inc/email-verification.php` — token generation, verification route
handler, user-verified check, and rate-limited AJAX resend endpoint
(`ps_resend_verify`).
- `inc/mail-diagnostic.php` — permanent admin tool under Tools → Mail Test
that exercises `wp_mail` and surfaces duration + PHPMailer error.
- v1.5.0 user migration in `inc/page-setup.php`: existing subscribers are
enrolled into the email verification flow on first admin load after the
upgrade (guarded by `ps_verify_migration_version` option).
- Newsletter consent checkbox + privacy policy link on the signup form.
- Newsletter unsubscribe flow: every subscription now stores a token;
`/newsletter-unsubscribe?token=…` flips the `unsubscribed` flag without
requiring a login.
- Profile page verify-required banner with resend button (AJAX).
- `peptide_starter_render_honeypot()` and `peptide_starter_csv_safe()`
helpers in `inc/helpers.php`.
- PHPUnit suites: `test-rate-limiter.php`, `test-auth-handlers.php`,
`test-email-verification.php`, `test-contact-handler.php`,
`test-newsletter.php`, `test-auth-gate.php`.
### Changed
- `functions.php` loads the five new modules in dependency order.
- Newsletter handler stores entries with `autoload=false` so a large
subscriber list no longer bloats every page load. Admin notice surfaces
once subscribers exceed 1000 (threshold is configurable).
- Newsletter handler collapses `success` and `duplicate` response states
into a single `ok` state — no longer leaks subscription status.
- Contact handler rejects sender names containing `\r`, `\n`, `,`, `<`,
`>` up front (header-injection defence) and tags mail failures with a
request ID so admins can correlate without storing message content.
- Fallback primary-menu function moved from `header.php` into
`inc/helpers.php` to eliminate redeclaration risk.
- Footer placeholder language links (`href="#"`) replaced with a
"Translations coming soon" note. Will render localized links once
per-language blogs exist.
- Footer copyright now renders through `peptide_starter_get_footer_copyright()`
so the customizer control is live (was dead before).
- Settings panel now implements a proper Tab / Shift+Tab focus trap and
saves + restores `document.body.style.overflow` so nested overlays
don't corrupt scroll state.
- Documentation heading IDs are slugified from heading text with a
collision counter so deep links survive reorders.
- `assets/js/auth.js`, `settings-panel.js`, `documentation.js`,
`theme.js`, `navigation.js` standardized on `const` / `let`.
### Removed
- Auto-login on registration (see PSEC-003).
- Placeholder-link spam in the footer (see Changed).
### Documentation
- `ARCHITECTURE.md` updated with the new `inc/` module list, authentication
data flow, security abuse-control overview, and verification route.
- `CONVENTIONS.md` documents the rate-limiter usage pattern, the honeypot
pattern, the `require_login` gate, and CSV-safe export.
- ADR-0001 (Frontend authentication and abuse-control strategy) governs
the trade-offs behind this release.
### Known follow-ups (out of scope; tracked as future ADRs)
- ADR-0003 — consolidate the six duplicated tool templates.
- ADR-0004 — migrate newsletter storage from `wp_options` to a dedicated
table once subscriber count exceeds ~1000.
---
## [1.5.0] - 2026-04-14
Theme extension release: navigation dropdowns, 9 page templates, branded
frontend auth UI, newsletter signup, contact/support panel, footer update,
and Research Modules cards on the front page. Superseded by v1.5.1 on the
same day — v1.5.0 shipped with the auth-surface issues captured in
ADR-0001.
---
## [1.3.2] - 2026-04-12
### Security