Files
peptide-starter-theme/inc/config.php
peptiderepo cb8b73f4ab 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>
2026-04-14 13:01:22 +07:00

123 lines
4.1 KiB
PHP

<?php
/**
* Security and Feature Configuration
*
* Single source of truth for every security threshold, cooldown, and
* toggle used by the theme. All values are filterable via
* peptide_starter_security_config so deployments can override without
* forking code.
*
* @see inc/rate-limiter.php — consumes login/register/contact/newsletter limits
* @see inc/auth-handlers.php — consumes registration + email-verify settings
* @see inc/email-verification.php — consumes token TTL + resend limit
* @see inc/contact-handler.php — consumes contact rate limit
* @see functions.php — includes this file
*
* What: Returns the security configuration array for all abuse controls.
* Who calls it: Any handler needing a threshold; called once per request.
* Dependencies: None — pure array + filter.
*
* @package peptide-starter
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Return the theme-wide security configuration.
*
* Every threshold in the theme must be read from this function; no magic
* numbers in handler files. Override with add_filter( 'peptide_starter_security_config', ... ).
*
* Cost note: this function is cheap and pure — no DB reads. Call it freely.
*
* @return array {
* @type array $rate_limits Per-action limit + window tuples.
* @type int $verify_token_ttl Seconds a verification token stays valid.
* @type int $verify_resend_limit Resends permitted per hour per IP+email.
* @type bool $email_verify_required Whether new registrations require email verification.
* @type bool $registration_enabled Master switch (separate from users_can_register).
* @type int $newsletter_autoload_threshold Subscribers before admin migration notice.
* @type array $contact_topics Allowlisted contact form topic slugs.
* @type int $username_min Minimum username length.
* @type int $username_max Maximum username length.
* @type int $password_min Minimum password length.
* }
*/
function peptide_starter_security_config() {
$defaults = array(
'rate_limits' => array(
// action => array( limit, window_seconds ).
'login' => array(
'limit' => 5,
'window' => 60,
),
'register' => array(
'limit' => 3,
'window' => HOUR_IN_SECONDS,
),
'contact' => array(
'limit' => 5,
'window' => HOUR_IN_SECONDS,
),
'newsletter' => array(
'limit' => 3,
'window' => HOUR_IN_SECONDS,
),
'verify_resend' => array(
'limit' => 2,
'window' => HOUR_IN_SECONDS,
),
),
'verify_token_ttl' => DAY_IN_SECONDS,
'email_verify_required' => true,
'registration_enabled' => true,
'newsletter_autoload_threshold' => 1000,
'contact_topics' => array( 'bug', 'feature', 'data', 'other' ),
'username_min' => 5,
'username_max' => 15,
'password_min' => 8,
);
/**
* Filter the theme's security configuration.
*
* @param array $defaults Default configuration.
*/
return apply_filters( 'peptide_starter_security_config', $defaults );
}
/**
* Shortcut to read a single top-level config key.
*
* @param string $key Top-level key (e.g. 'verify_token_ttl').
* @param mixed $fallback Value returned when the key is missing.
* @return mixed Configured value or $fallback.
*/
function peptide_starter_config_get( $key, $fallback = null ) {
$config = peptide_starter_security_config();
return array_key_exists( $key, $config ) ? $config[ $key ] : $fallback;
}
/**
* Read a rate limit tuple (limit, window) for a given action.
*
* @param string $action Action slug matching a key in rate_limits.
* @return array { 'limit' => int, 'window' => int } with safe defaults.
*/
function peptide_starter_config_rate_limit( $action ) {
$limits = peptide_starter_config_get( 'rate_limits', array() );
if ( isset( $limits[ $action ]['limit'], $limits[ $action ]['window'] ) ) {
return array(
'limit' => (int) $limits[ $action ]['limit'],
'window' => (int) $limits[ $action ]['window'],
);
}
return array(
'limit' => 5,
'window' => HOUR_IN_SECONDS,
);
}