PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 2.9.5
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v2.9.5
3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
vigilante / includes / class-settings.php

class-settings.php in Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… 2.9.5, at includes/class-settings.php

935 lines 38.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Settings Class
4 *
5 * Centralized settings management with default values
6 *
7 * @package Vigilante
8 */
9
10 // Prevent direct access
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Class Vigilante_Settings
17 *
18 * Handles all plugin settings with defaults, getters and setters
19 */
20 class Vigilante_Settings {
21
22 /**
23 * Option name in database
24 */
25 const OPTION_NAME = 'vigilante_options';
26
27 /**
28 * Cached options
29 *
30 * @var array|null
31 */
32 private $options = null;
33
34 /**
35 * Default options structure
36 *
37 * @var array
38 */
39 private $defaults;
40
41 /**
42 * Constructor
43 */
44 public function __construct() {
45 $this->defaults = $this->get_default_options();
46 }
47
48 /**
49 * Get all default options
50 *
51 * @return array Complete default options array
52 */
53 public function get_default_options() {
54 return array(
55 // Module toggles - 8 modules that match tabs
56 'modules' => array(
57 'firewall' => true,
58 'security_headers' => true,
59 'login_security' => true,
60 'rest_api_security'=> true,
61 'user_security' => true,
62 'wp_hardening' => true,
63 'file_integrity' => true,
64 'activity_log' => true,
65 ),
66
67 // Firewall settings (includes htaccess, rate limiting, file protection)
68 'firewall' => array(
69 // Request filtering (PHP-based)
70 'block_bad_query_strings' => true,
71 'block_sql_injection' => true,
72 'block_xss_attacks' => true,
73 'block_file_inclusion' => true,
74 'block_directory_traversal' => true,
75
76 // Bot protection
77 'block_bad_bots' => true,
78 'block_empty_user_agent' => false,
79 'block_http_1_0' => false,
80
81 // Rate limiting
82 'rate_limiting' => array(
83 'enabled' => true,
84 'requests_per_minute' => 120,
85 'block_duration' => 300,
86 'progressive' => false,
87 'max_block_duration' => 86400,
88 ),
89
90 // IP management
91 'ip_whitelist' => array(),
92 'ip_blacklist' => array(),
93
94 // Proxy / CDN: forwarded header to trust for the visitor IP.
95 // Empty = trust only REMOTE_ADDR (the real connection, unspoofable).
96 'trusted_proxy_header' => '',
97
98 // User-Agent management
99 'ua_whitelist' => array(),
100 'ua_blacklist' => array(),
101 'country_blocking' => array(
102 'enabled' => false,
103 'mode' => 'blacklist',
104 'countries' => array(),
105 ),
106
107 // File protection (htaccess-based)
108 'disable_directory_browsing' => true,
109 'protect_wp_config' => true,
110 'protect_htaccess' => true,
111 'protect_wp_includes' => true,
112 'protect_uploads_php' => true,
113 'protect_sensitive_files' => true,
114 // Off by default — only safe when host has a real server-side cron job
115 // calling wp-cron.php; otherwise scheduled tasks stop running silently.
116 'protect_wp_cron' => false,
117 'block_php_in_plugins' => false,
118 'block_php_in_themes' => false,
119 'limit_http_methods' => true,
120 // All methods needed for WordPress core, Gutenberg, REST API, and page builders
121 'allowed_http_methods' => array( 'GET', 'POST', 'HEAD', 'OPTIONS', 'PUT', 'PATCH', 'DELETE' ),
122 'protected_file_extensions' => array(
123 'htaccess', 'htpasswd', 'ini', 'log', 'sql',
124 'bak', 'old', 'tmp', 'swp', 'save', 'backup'
125 ),
126 ),
127
128 // Security Headers settings (includes HTTPS enforcer)
129 'security_headers' => array(
130 'enabled' => true,
131
132 // Basic headers
133 'x_frame_options' => 'SAMEORIGIN',
134 'x_content_type_options' => true,
135 'referrer_policy' => 'strict-origin-when-cross-origin',
136
137 // HSTS
138 'hsts' => array(
139 'enabled' => false,
140 'max_age' => 31536000,
141 'include_subdomains' => false,
142 'preload' => false,
143 ),
144
145 // Permissions Policy
146 'permissions_policy' => array(
147 'enabled' => true,
148 'geolocation' => '()',
149 'microphone' => '()',
150 'camera' => '()',
151 'payment' => '(self)',
152 'usb' => '()',
153 ),
154
155 // CSP - WordPress/Gutenberg compatible defaults
156 // Note: blob: is required in frame-src and worker-src for the block editor
157 'csp' => array(
158 'enabled' => true,
159 'report_only' => false,
160 'report_uri' => '',
161 'directives' => array(
162 'default-src' => "'self'",
163 'script-src' => "'self' 'unsafe-inline' 'unsafe-eval' https:",
164 'style-src' => "'self' 'unsafe-inline' https:",
165 'img-src' => "'self' data: https: blob:",
166 'font-src' => "'self' data: https:",
167 'connect-src' => "'self' https: wss:",
168 'media-src' => "'self' https: blob:",
169 'frame-src' => "'self' https: blob:",
170 'frame-ancestors' => "'self'",
171 'base-uri' => "'self'",
172 'form-action' => "'self' https:",
173 'object-src' => "'none'",
174 'worker-src' => "'self' blob:",
175 'upgrade-insecure-requests'=> true,
176 ),
177 ),
178
179 // Cross-origin policies
180 'cross_origin_policies' => array(
181 'embedder_policy' => 'unsafe-none',
182 'opener_policy' => 'same-origin-allow-popups',
183 'resource_policy' => 'cross-origin',
184 ),
185
186 // HTTPS Enforcer (moved from separate module)
187 'force_https' => true,
188 'redirect_http_to_https' => true,
189 'fix_mixed_content' => true,
190
191 // Server Protection (moved from firewall in v2.0.0)
192 'hide_server_signature' => true,
193 'remove_fingerprinting_headers' => true,
194 ),
195
196 // Login Security settings
197 'login_security' => array(
198 'enabled' => true,
199 'max_attempts' => 5,
200 'lockout_duration' => 1800,
201 'lockout_increment' => true,
202 'max_lockout_duration' => 86400,
203 'hide_login_errors' => true,
204 'disable_xmlrpc' => true,
205 'disable_xmlrpc_pingback' => true,
206 'disable_application_passwords' => false,
207 'notify_on_lockout' => false,
208 'notify_on_admin_login' => false,
209 'ip_whitelist' => array(),
210 'custom_login_url' => '',
211 'notify_on_login_url_change' => true,
212 // Two-Factor Authentication
213 'two_factor' => array(
214 'enabled' => false,
215 'method' => 'email',
216 'enforced_roles' => array( 'administrator', 'editor' ),
217 'excluded_users' => array(),
218 'remember_device_days' => 30,
219 'allow_remember_device' => false,
220 'code_expiry_minutes' => 10,
221 'max_attempts' => 3,
222 'email_from_name' => '',
223 'notify_on_enable' => true,
224 'grace_period_days' => 3,
225 ),
226 ),
227
228 // REST API Security settings
229 'rest_api_security' => array(
230 'enabled' => true,
231 'mode' => 'selective',
232 'block_user_enumeration' => true,
233 'disable_jsonp' => true,
234 // Empty by default: /wp/v2/users used to live here, but that
235 // duplicated the dedicated "Block user enumeration" toggle.
236 // Now there is one knob = one behaviour. If you want to
237 // protect additional endpoints in selective mode, add them
238 // explicitly via this setting (or via a filter).
239 'protected_endpoints' => array(),
240 'allowed_public_endpoints' => array(
241 '/wp/v2/posts',
242 '/wp/v2/pages',
243 '/wp/v2/categories',
244 '/wp/v2/tags',
245 '/oembed/',
246 ),
247 'plugin_compatibility' => array(
248 'woocommerce' => true,
249 'contact_form_7' => true,
250 'elementor' => true,
251 ),
252 ),
253
254 // User Security settings
255 'user_security' => array(
256 'enabled' => true,
257 'block_insecure_usernames'=> true,
258 'insecure_usernames' => array(
259 'admin', 'administrator', 'user', 'test', 'guest',
260 'info', 'root', 'adm', 'sysadmin', 'support',
261 'webmaster', 'master', 'owner', 'manager', 'demo',
262 ),
263 'warn_existing_insecure' => true,
264 'block_author_scanning' => true,
265 'force_strong_passwords' => true,
266 'min_password_length' => 12,
267
268 // Granular password policy. Applies only while
269 // force_strong_passwords is on. Defaults reproduce the previous
270 // all-requirements behaviour so existing sites keep the same
271 // rules until the admin relaxes them. block_username is the only
272 // new opt-in rule (off by default to avoid rejecting passwords
273 // that were valid before). affected_roles empty = all roles.
274 'password_policy' => array(
275 'require_uppercase' => true,
276 'require_lowercase' => true,
277 'require_number' => true,
278 'require_special' => true,
279 'block_common' => true,
280 'block_username' => false,
281 'affected_roles' => array(),
282 ),
283
284 'prevent_display_name_login_match' => true,
285
286 // Admin monitoring
287 'admin_monitoring' => array(
288 'alert_new_admin' => false,
289 'alert_admin_email_change' => false,
290 'alert_permission_elevation' => false,
291 'alert_admin_password_change' => false,
292 ),
293
294 // Force password reset (no options, uses native WordPress flow)
295
296 // Registration approval
297 'registration_approval' => array(
298 'enabled' => false,
299 'notify_admin' => false,
300 'auto_reject_days' => 0,
301 'affected_roles' => array( 'subscriber' ),
302 ),
303
304 // Session management
305 'session_management' => array(
306 'enabled' => true,
307 'show_in_profile' => true,
308 ),
309
310 // Session limits
311 'session_limits' => array(
312 'enabled' => false,
313 'max_sessions' => 3,
314 'behavior' => 'close_oldest',
315 'exclude_admins' => false,
316 ),
317
318 // Password expiration
319 'password_expiration' => array(
320 'enabled' => true,
321 'expire_days' => 90,
322 'warning_days' => 14,
323 'affected_roles' => array( 'administrator', 'editor' ),
324 'excluded_users' => array(),
325 'password_history' => 3,
326 'send_reminder' => false,
327 ),
328
329 // Email verification
330 'email_verification' => array(
331 'enabled' => false,
332 'token_expiry_hours' => 24,
333 'allow_resend' => true,
334 'auto_delete_days' => 7,
335 ),
336 ),
337
338 // WordPress Hardening (combines wp-config, comments, feeds, head cleaner)
339 'wp_hardening' => array(
340 'enabled' => true,
341
342 // wp-config security
343 'disallow_file_edit' => true,
344 'disallow_file_mods' => false,
345 'force_ssl_admin' => true,
346 'wp_debug' => true,
347 // Off by default — only safe when host has a real server-side cron job;
348 // pairs with firewall.protect_wp_cron to block both internal triggering
349 // (this constant) and external HTTP abuse (the .htaccess rule).
350 'disable_wp_cron' => false,
351
352 // Comment security
353 'disable_pingbacks' => true,
354 'disable_trackbacks' => true,
355 'require_comment_moderation' => true,
356 'close_old_comments' => false,
357 'close_comments_after_days' => 30,
358 'honeypot_comments' => true,
359
360 // Head cleaner
361 'remove_wp_generator' => true,
362 'remove_wp_version_assets' => false,
363 'remove_rsd_link' => true,
364 'remove_wlw_manifest' => true,
365 'remove_shortlink' => true,
366 'remove_rest_api_link' => false,
367
368 // Feed manager
369 'disable_feeds' => false,
370 'disable_if_no_content' => true,
371 'remove_feed_version' => true,
372 ),
373
374 // File Integrity settings
375 'file_integrity' => array(
376 'enabled' => true,
377 'scan_core' => true,
378 'scan_plugins' => true,
379 'scan_themes' => true,
380 'scan_uploads' => true,
381 'scan_critical_config' => true,
382 'check_closed_plugins' => true,
383 'auto_scan' => true,
384 'scan_frequency' => 'daily',
385 'notify_level' => 'suspicious_only',
386 'instant_alert' => false,
387 'excluded_paths' => array(
388 'wp-content/cache',
389 ),
390 'excluded_extensions' => array(
391 // Translations (regenerated per-locale, never in checksums).
392 '.po', '.mo', '.pot',
393 // Binary images (cosmetic, not executable; often rewritten by image-optimizer plugins).
394 '.jpg', '.jpeg', '.png', '.gif', '.ico', '.webp', '.avif',
395 // Stylesheets: frequently rewritten by themes and optimizer
396 // plugins, a common source of post-update false positives.
397 // Strict-mode users can remove it (CSS injection is still a
398 // vector, defended primarily by CSP in the headers module).
399 '.css',
400 ),
401 'suspicious_patterns' => array(
402 'eval(',
403 'base64_decode(',
404 'gzinflate(',
405 'str_rot13(',
406 'exec(',
407 'shell_exec(',
408 'system(',
409 'passthru(',
410 'assert(',
411 ),
412 ),
413
414 // Activity Log settings
415 'activity_log' => array(
416 'retention_days' => 30,
417 'max_entries' => 10000,
418 'log_logins' => true,
419 'log_failed_logins' => true,
420 'log_user_changes' => true,
421 'log_post_changes' => true,
422 'log_plugin_changes' => true,
423 'log_theme_changes' => true,
424 'log_option_changes' => false,
425 'log_file_changes' => true,
426 'log_comments' => true,
427 'log_media' => true,
428 'excluded_users' => array(),
429 'excluded_ips' => array(),
430 'tracked_options' => array(),
431 ),
432
433 // Backup settings
434 'backup' => array(
435 'auto_backup' => true,
436 'backup_before_update' => true,
437 'keep_backups' => 5,
438 ),
439
440 // Notification settings (centralized recipients for all admin emails)
441 'email' => array(
442 'send_to_admin_email' => true,
443 'additional_recipients' => array(),
444 'send_deactivation_email' => true,
445 ),
446
447 // Advanced settings
448 'advanced' => array(
449 'remove_readme' => true,
450 'remove_license' => true,
451 'block_author_archives' => false,
452 'disable_embeds' => false,
453 'uninstall_cleanup' => true,
454 'debug_mode' => false,
455 ),
456
457 // Security Analyzer (v2.1.0) — on-demand + weekly Security Check
458 'security_analyzer' => array(
459 'weekly_scan_enabled' => true,
460 'email_on_regression' => false,
461 ),
462
463 // Audit Alerts (v2.8.0) — alerting layer on top of Security Audit.
464 // The engine subscribes to logged events and only runs when the
465 // Security Audit (activity_log) module is enabled. Opt-in: both
466 // legs start OFF so it never duplicates the per-module emails that
467 // already exist (User Security admin monitoring, Plugin Status...).
468 'audit_alerts' => array(
469 // Shared anti-repeat cooldown (minutes). After an alert, do not
470 // send another about the same thing (same event type for
471 // immediate, same category for threshold) until this passes.
472 // Prevents a flood during a sustained attack.
473 'cooldown_minutes' => 60,
474 // #38 Immediate alerts: selected event types email right away.
475 'immediate' => array(
476 'enabled' => false,
477 // Alert on any logged event at or above this severity. A new
478 // admin, a closed plugin or a privilege escalation are all
479 // logged as "critical", so "critical" already covers them.
480 'min_severity' => 'critical', // 'critical' | 'warning'
481 ),
482 // #10 Threshold alerts: N events of a category within a window.
483 'threshold' => array(
484 'enabled' => false,
485 'window' => '1h', // 30m | 1h | 6h | 24h
486 // Per-category trigger counts (warning/critical events only);
487 // 0 disables that category. Covers every event type that can
488 // log a warning or critical. Keep in sync with
489 // Vigilante_Audit_Alerts::category_labels().
490 'categories' => array(
491 'firewall' => 50,
492 'login' => 20,
493 'user' => 5,
494 'plugin' => 0,
495 'file' => 0,
496 'security' => 0,
497 'system' => 0,
498 'settings' => 0,
499 'theme' => 0,
500 'content' => 0,
501 'comment' => 0,
502 'media' => 0,
503 ),
504 ),
505 ),
506 );
507 }
508
509 /**
510 * Get all options (merged with defaults)
511 *
512 * @return array All options
513 */
514 public function get_all_options() {
515 if ( null === $this->options ) {
516 $saved = get_option( self::OPTION_NAME, array() );
517 $this->options = $this->array_merge_deep( $this->get_default_options(), $saved );
518 }
519 return $this->options;
520 }
521
522 /**
523 * Deep merge arrays
524 *
525 * @param array $defaults Default values.
526 * @param array $saved Saved values.
527 * @return array Merged array.
528 */
529 private function array_merge_deep( $defaults, $saved ) {
530 $result = $defaults;
531
532 foreach ( $saved as $key => $value ) {
533 if ( is_array( $value ) && isset( $result[ $key ] ) && is_array( $result[ $key ] ) ) {
534 $result[ $key ] = $this->array_merge_deep( $result[ $key ], $value );
535 } else {
536 $result[ $key ] = $value;
537 }
538 }
539
540 return $result;
541 }
542
543 /**
544 * Get a specific section
545 *
546 * @param string $section Section name.
547 * @return array Section options.
548 */
549 public function get_section( $section ) {
550 $options = $this->get_all_options();
551 return isset( $options[ $section ] ) ? $options[ $section ] : array();
552 }
553
554 /**
555 * Get a specific option
556 *
557 * @param string $section Section name.
558 * @param string $key Option key.
559 * @param mixed $default Default value.
560 * @return mixed Option value.
561 */
562 public function get_option( $section, $key, $default = null ) {
563 $options = $this->get_all_options();
564
565 if ( isset( $options[ $section ][ $key ] ) ) {
566 return $options[ $section ][ $key ];
567 }
568
569 return $default;
570 }
571
572 /**
573 * Check if a module is enabled
574 *
575 * @param string $module Module name.
576 * @return bool Whether module is enabled.
577 */
578 public function is_module_enabled( $module ) {
579 $options = $this->get_all_options();
580 return ! empty( $options['modules'][ $module ] );
581 }
582
583 /**
584 * Save options
585 *
586 * @param array $options Options to save.
587 * @return bool Success status.
588 */
589 public function save_options( $options ) {
590 $this->options = null;
591 return update_option( self::OPTION_NAME, $options );
592 }
593
594 /**
595 * Update a section
596 *
597 * @param string $section Section name.
598 * @param array $data Section data.
599 * @return bool Success status.
600 */
601 public function update_section( $section, $data ) {
602 $options = get_option( self::OPTION_NAME, array() );
603 $options[ $section ] = $data;
604 $this->options = null;
605 return update_option( self::OPTION_NAME, $options );
606 }
607
608 /**
609 * Update multiple sections at once
610 *
611 * @param array $sections Associative array of section => data.
612 * @return bool Success status.
613 */
614 public function update_options( $sections ) {
615 $options = get_option( self::OPTION_NAME, array() );
616
617 foreach ( $sections as $section => $data ) {
618 $options[ $section ] = $data;
619 }
620
621 $this->options = null;
622 return update_option( self::OPTION_NAME, $options );
623 }
624
625 /**
626 * Clear the options cache
627 */
628 public function clear_cache() {
629 $this->options = null;
630 wp_cache_delete( self::OPTION_NAME, 'options' );
631 }
632
633 /**
634 * Get presets with descriptions
635 *
636 * @return array Presets configuration.
637 */
638 public function get_presets() {
639 return array(
640 'standard' => array(
641 'name' => __( 'Standard', 'vigilante' ),
642 'description' => __( 'Balanced security suitable for most websites. Enables all modules with sensible defaults.', 'vigilante' ),
643 'modules' => array(
644 'firewall' => true,
645 'security_headers' => true,
646 'login_security' => true,
647 'rest_api_security'=> true,
648 'user_security' => true,
649 'wp_hardening' => true,
650 'file_integrity' => true,
651 'activity_log' => true,
652 ),
653 'firewall' => array(
654 'block_bad_query_strings' => true,
655 'block_sql_injection' => true,
656 'block_xss_attacks' => true,
657 'rate_limiting' => array(
658 'enabled' => true,
659 'requests_per_minute' => 120,
660 ),
661 ),
662 'login_security' => array(
663 'max_attempts' => 5,
664 'lockout_duration' => 1800,
665 'disable_xmlrpc' => true,
666 ),
667 'rest_api_security' => array(
668 'mode' => 'selective',
669 ),
670 'user_security' => array(
671 'prevent_display_name_login_match' => true,
672 ),
673 'file_integrity' => array(
674 'notify_level' => 'suspicious_only',
675 ),
676 ),
677
678 'maximum' => array(
679 'name' => __( 'Maximum Security', 'vigilante' ),
680 'description' => __( 'Strictest settings for high-security sites. CSP is set to report-only mode to prevent breaking the admin interface.', 'vigilante' ),
681 'modules' => array(
682 'firewall' => true,
683 'security_headers' => true,
684 'login_security' => true,
685 'rest_api_security'=> true,
686 'user_security' => true,
687 'wp_hardening' => true,
688 'file_integrity' => true,
689 'activity_log' => true,
690 ),
691 'firewall' => array(
692 'block_bad_query_strings' => true,
693 'block_sql_injection' => true,
694 'block_xss_attacks' => true,
695 'block_file_inclusion' => true,
696 'block_directory_traversal' => true,
697 'block_bad_bots' => true,
698 'block_empty_user_agent' => true,
699 'rate_limiting' => array(
700 'enabled' => true,
701 'requests_per_minute' => 60,
702 'block_duration' => 600,
703 'progressive' => true,
704 'max_block_duration' => 86400,
705 ),
706 ),
707 'security_headers' => array(
708 'x_frame_options' => 'DENY',
709 // HSTS is intentionally NOT enabled by Maximum: forcing HSTS on a site
710 // that doesn't have a healthy HTTPS setup (or temporarily falls back to
711 // HTTP) locks visitors out for the full max_age. Leaving HSTS off keeps
712 // it as an explicit opt-in decision per site.
713 'csp' => array(
714 'enabled' => true,
715 'report_only' => false,
716 'directives' => array(
717 'default-src' => "'self'",
718 'script-src' => "'self' 'unsafe-inline' 'unsafe-eval'",
719 'style-src' => "'self' 'unsafe-inline'",
720 'img-src' => "'self' data: https: blob:",
721 'font-src' => "'self' data:",
722 'connect-src' => "'self' https:",
723 'frame-src' => "'self' blob:",
724 'frame-ancestors' => "'none'",
725 'worker-src' => "'self' blob:",
726 'object-src' => "'none'",
727 'base-uri' => "'self'",
728 ),
729 ),
730 ),
731 'rest_api_security' => array(
732 'mode' => 'authenticated_only',
733 ),
734 'login_security' => array(
735 'max_attempts' => 3,
736 'lockout_duration' => 3600,
737 'lockout_increment' => true,
738 'disable_xmlrpc' => true,
739 'notify_on_lockout' => true,
740 'notify_on_admin_login' => true,
741 ),
742 'wp_hardening' => array(
743 'disallow_file_edit' => true,
744 'disallow_file_mods' => true,
745 // close_old_comments is intentionally NOT touched by Maximum:
746 // it would unilaterally close discussion on every old post,
747 // which is a content decision, not a security one.
748 ),
749 'user_security' => array(
750 'prevent_display_name_login_match' => true,
751 'min_password_length' => 16,
752 'password_policy' => array(
753 'require_uppercase' => true,
754 'require_lowercase' => true,
755 'require_number' => true,
756 'require_special' => true,
757 'block_common' => true,
758 'block_username' => true,
759 'affected_roles' => array(),
760 ),
761 'admin_monitoring' => array(
762 'alert_new_admin' => true,
763 'alert_admin_email_change' => true,
764 'alert_permission_elevation' => true,
765 'alert_admin_password_change' => true,
766 ),
767 'registration_approval' => array(
768 'enabled' => true,
769 'notify_admin' => true,
770 'auto_reject_days' => 7,
771 'affected_roles' => array( 'subscriber', 'contributor', 'author', 'editor' ),
772 ),
773 'session_limits' => array(
774 'enabled' => true,
775 'max_sessions' => 1,
776 'behavior' => 'close_oldest',
777 'exclude_admins' => false,
778 ),
779 'password_expiration' => array(
780 'enabled' => true,
781 'expire_days' => 30,
782 'warning_days' => 7,
783 'affected_roles' => array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ),
784 'password_history' => 5,
785 'send_reminder' => true,
786 ),
787 'email_verification' => array(
788 'enabled' => true,
789 'token_expiry_hours' => 24,
790 'allow_resend' => true,
791 'auto_delete_days' => 3,
792 ),
793 ),
794 'file_integrity' => array(
795 'scan_core' => true,
796 'scan_plugins' => true,
797 'scan_themes' => true,
798 'scan_uploads' => true,
799 'scan_critical_config' => true,
800 'auto_scan' => true,
801 'scan_frequency' => 'daily',
802 'notify_level' => 'all',
803 'instant_alert' => true,
804 ),
805 'activity_log' => array(
806 'log_logins' => true,
807 'log_failed_logins' => true,
808 'log_user_changes' => true,
809 'log_post_changes' => true,
810 'log_plugin_changes' => true,
811 'log_theme_changes' => true,
812 'log_option_changes' => true,
813 'log_file_changes' => true,
814 'log_comments' => true,
815 'log_media' => true,
816 ),
817 ),
818 );
819 }
820
821 /**
822 * Get module labels for display
823 *
824 * @return array Module labels.
825 */
826 public function get_module_labels() {
827 return array(
828 'firewall' => __( 'Firewall', 'vigilante' ),
829 'security_headers' => __( 'Security Headers', 'vigilante' ),
830 'login_security' => __( 'Login Security', 'vigilante' ),
831 'rest_api_security'=> __( 'REST API Security', 'vigilante' ),
832 'user_security' => __( 'User Security', 'vigilante' ),
833 'wp_hardening' => __( 'WordPress Hardening', 'vigilante' ),
834 'file_integrity' => __( 'File Integrity', 'vigilante' ),
835 'activity_log' => __( 'Security Audit', 'vigilante' ),
836 );
837 }
838
839 /**
840 * Get module descriptions for display
841 *
842 * @return array Module descriptions.
843 */
844 public function get_module_descriptions() {
845 return array(
846 'firewall' => __( 'Blocks malicious requests, SQL injection, XSS attacks, and bad bots. Includes rate limiting and file protection.', 'vigilante' ),
847 'security_headers' => __( 'Adds HTTP security headers like CSP, HSTS, X-Frame-Options. Forces HTTPS and fixes mixed content.', 'vigilante' ),
848 'login_security' => __( 'Brute force protection, 2FA, login attempt limits, XML-RPC control, and notifications.', 'vigilante' ),
849 'rest_api_security'=> __( 'Controls REST API access, blocks user enumeration, and protects sensitive endpoints.', 'vigilante' ),
850 'user_security' => __( 'Blocks insecure usernames, enforces strong passwords, and prevents author scanning.', 'vigilante' ),
851 'wp_hardening' => __( 'Hardens wp-config.php, manages comments, cleans header output, and controls feeds.', 'vigilante' ),
852 'file_integrity' => __( 'Scans WordPress core, plugins, and themes for unauthorized changes and suspicious code.', 'vigilante' ),
853 'activity_log' => __( 'Records user actions, logins, content changes, and security events for security auditing.', 'vigilante' ),
854 );
855 }
856
857 /**
858 * Validate options before saving
859 *
860 * @param array $input Raw input to validate.
861 * @return array Validated options.
862 */
863 public function validate_options( $input ) {
864 $validated = array();
865 $defaults = $this->get_default_options();
866
867 // Validate each section that exists in input
868 foreach ( $input as $section => $data ) {
869 if ( ! is_array( $data ) ) {
870 continue;
871 }
872
873 if ( 'modules' === $section ) {
874 // Validate modules (booleans)
875 foreach ( $defaults['modules'] as $module => $default_value ) {
876 $validated['modules'][ $module ] = isset( $data[ $module ] )
877 ? (bool) $data[ $module ]
878 : false;
879 }
880 } elseif ( isset( $defaults[ $section ] ) ) {
881 // Validate other sections using generic validator
882 $validated[ $section ] = $this->validate_section( $data, $defaults[ $section ] );
883 }
884 }
885
886 return apply_filters( 'vigilante_validate_options', $validated, $input );
887 }
888
889 /**
890 * Validate a section based on defaults
891 *
892 * @param array $input Input values.
893 * @param array $defaults Default values.
894 * @return array Validated values.
895 */
896 private function validate_section( $input, $defaults ) {
897 $validated = array();
898
899 foreach ( $defaults as $key => $default_value ) {
900 if ( ! isset( $input[ $key ] ) ) {
901 $validated[ $key ] = $default_value;
902 continue;
903 }
904
905 $value = $input[ $key ];
906
907 if ( is_bool( $default_value ) ) {
908 $validated[ $key ] = (bool) $value;
909 } elseif ( is_int( $default_value ) ) {
910 $validated[ $key ] = intval( $value );
911 } elseif ( is_array( $default_value ) ) {
912 if ( is_array( $value ) ) {
913 $validated[ $key ] = $this->validate_section( $value, $default_value );
914 } else {
915 $validated[ $key ] = $default_value;
916 }
917 } else {
918 $validated[ $key ] = sanitize_text_field( $value );
919 }
920 }
921
922 // Include any extra keys from input
923 foreach ( $input as $key => $value ) {
924 if ( ! isset( $validated[ $key ] ) ) {
925 if ( is_array( $value ) ) {
926 $validated[ $key ] = array_map( 'sanitize_text_field', $value );
927 } else {
928 $validated[ $key ] = sanitize_text_field( $value );
929 }
930 }
931 }
932
933 return $validated;
934 }
935 }