default. Values are the types and spellings the readers already used, so * moving a read here changes nothing it returns. */ const DEFAULTS = array( // Login security. 'csdt_devtools_2fa_method' => 'off', 'csdt_devtools_2fa_force_admins' => '0', 'csdt_devtools_2fa_grace_logins' => '0', 'csdt_devtools_login_hide_enabled' => '0', 'csdt_devtools_login_slug' => '', 'csdt_devtools_brute_force_enabled' => '1', 'csdt_devtools_brute_force_attempts' => '5', 'csdt_devtools_brute_force_lockout' => '10', 'csdt_devtools_bf_auto_block_threshold' => '10', 'csdt_devtools_enum_protect' => '1', 'csdt_devtools_session_duration' => 'default', 'csdt_devtools_passkey_rp_id' => '', 'csdt_honeypot_2fa_enabled' => '0', 'csdt_devtools_disable_app_passwords' => '0', 'csdt_pwned_check_mode' => 'off', 'csdt_password_min_length' => '0', 'csdt_2fa_required_roles' => '', // array of role slugs once saved; '' = none // Blocking. 'csdt_autoblock_enabled' => '0', 'csdt_autoblock_threshold' => 30, 'csdt_geoblock_enabled' => '0', 'csdt_block_user_enum' => '0', 'csdt_disable_xmlrpc' => '0', // Headers and CSP. 'csdt_devtools_csp_enabled' => '0', 'csdt_devtools_csp_mode' => 'enforce', 'csdt_csp_nonces_enabled' => '0', 'csdt_devtools_safe_headers_enabled' => '0', 'csdt_devtools_hide_wp_version' => '0', 'csdt_crossorigin_scripts' => '0', // Detection and monitoring. 'csdt_threat_monitor_enabled' => '1', 'csdt_threat_new_admin_enabled' => '1', 'csdt_threat_probe_enabled' => '0', 'csdt_threat_probe_threshold' => '25', 'csdt_ssh_monitor_enabled' => '0', 'csdt_ssh_monitor_threshold' => '10', 'csdt_php_error_monitor_enabled' => '1', 'csdt_php_error_monitor_threshold' => '1', 'csdt_devtools_perf_monitor_enabled' => '0', // Vulnerable-component policy (CSDT_Vuln_Autopatch). Report only; updates are an admin's click. 'csdt_vuln_autopatch_protected' => '', // Comment guard opt-ins (CSDT_Comment_Guard) and the site-wide comment ceiling // (CSDT_Comment_Flood). Only reporting is on by default. 'csdt_comment_guard_enforce_block_markup' => '0', 'csdt_comment_guard_refuse_flagged' => '0', 'csdt_comment_guard_refuse_duplicates' => '0', 'csdt_comment_guard_block_links' => '0', 'csdt_comment_ceiling_mode' => 'report', 'csdt_comment_ceiling_limit' => '30', 'csdt_comment_min_gap' => '0', // Where alerts and mail go. 'csdt_notify_telegram_enabled' => '1', 'csdt_notify_email' => '', 'csdt_scan_schedule_email' => '1', 'csdt_telegram_login_valid_user' => '0', 'csdt_telegram_login_invalid_user' => '0', 'csdt_telegram_vuln_found' => '1', 'csdt_devtools_smtp_enabled' => '0', 'csdt_remediation_telemetry' => '0', // Weekly report and outbound webhook. The report is a read-only summary to the site's // own address, so it is on; the webhook sends nothing until a URL is saved. 'csdt_weekly_report_enabled' => '1', 'csdt_weekly_report_day' => '1', 'csdt_weekly_report_to' => '', 'csdt_webhook_url' => '', 'csdt_webhook_secret' => '', 'csdt_webhook_format' => 'slack', 'csdt_webhook_events' => 'critical,autoblock,new_admin,vuln,weekly', // Export to PDF button on posts. On: it adds a button and loads nothing else until clicked. 'csdt_pdf_export_enabled' => '1', // Live Traffic feed (CSDT_Live_Traffic). Off: while on it writes one row per uncached // request, which is a cost the site owner should choose, not inherit. 'csdt_live_traffic_enabled' => '0', // PHP Advisory Alerts (CSDT_Php_Advisory). On: alerts only, it changes nothing on the // site, and the request carries no site data. Owner decision 2026-10-21. 'csdt_php_advisory_alerts' => '0', // Out of Band Heartbeat (CSDT_Uptime_Heartbeat). Off: on, it posts to the owner's own // endpoint every five minutes. 'csdt_uptime_enabled' => '0', // Upgrade Verification (CSDT_Upgrade_Verify). On: it contacts only this site, and it // changes nothing (report only). Pass alerts off: a failure is what the owner needs. 'csdt_upgrade_verify_enabled' => '1', 'csdt_upgrade_verify_notify_pass' => '0', ); /** Names already reported as unmapped this request, so a hot path logs once. */ private static $reported = array(); /** * An option's stored value, or its declared default when it was never set. * * An unmapped name is a programming error the build test catches; at runtime it is logged * once and read with WordPress's own default (false), never a fatal. * * @param string $name Option name. * @return mixed */ public static function get( string $name ) { if ( ! array_key_exists( $name, self::DEFAULTS ) ) { if ( ! isset( self::$reported[ $name ] ) ) { self::$reported[ $name ] = true; $line = 'CSDT_Options: "' . $name . '" has no declared default; read with false'; if ( is_callable( array( 'CSDT_DevTools', 'log' ) ) ) { CSDT_DevTools::log( $line ); } else { error_log( $line ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- the plugin class is not loaded, so PHP's log is the only sink } } return get_option( $name ); } return get_option( $name, self::DEFAULTS[ $name ] ); } /** * An option's declared default. * * @param string $name Option name. * @return mixed The default, or null when the option is not mapped. */ public static function default_of( string $name ) { return self::DEFAULTS[ $name ] ?? null; } }