Real changes since 1.0.0, all live-verified before this release: - Firewall: rule corpus expanded 19 -> 43 rules, real OWASP-CRS-equivalent coverage (XXE, SSRF, session fixation, Log4Shell/JNDI, scanner-tool detection, deeper SQL injection/XSS/PHP-injection). - Fixed a real bug: a quarantined file's severity badge and its content analysis score could disagree with no explanation (e.g. a benign file showing CRITICAL next to Score 0); both are now derived consistently and shown together. - ARGUS now always keeps itself updated, and can optionally do the same for every other installed plugin and theme (Settings, on by default) -- uses WordPress's own native update system, nothing custom. - Global Threat Intelligence is now opt-in, not automatic -- a single click on its own page, with an honest, specific description of exactly what's shared (an IP address, a reason code, a confidence score, a country). Previously connected automatically on activation. - New first-run Welcome screen after activation: confirms what's already protecting the site, and surfaces the few real optional choices in one place. - Dashboard: running version now visible in the header; new "IPs Tracked" and "ANIS Protections" metrics. - Full WordPress.org Plugin Directory readiness audit performed against this codebase. Two real compliance issues found and fixed (see above: Global Threat Intelligence's default, and the self-update mechanism, which is excluded from this build entirely -- WordPress.org prohibits a plugin from using any update channel other than its own, even an inert one). This release is still self-distributed, not a WordPress.org submission -- that remains a future step. Verified before publishing: this exact ZIP was installed, activated (14 admin pages loaded clean, zero PHP errors/warnings), and uninstalled (zero leftover database tables or options) in a fresh, disposable WordPress + MySQL environment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
130 lines
4.0 KiB
PHP
130 lines
4.0 KiB
PHP
<?php
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
class Argus_Settings {
|
|
|
|
const OPTION = 'argus_wpd_settings';
|
|
|
|
const MODE_MONITOR = 'monitor';
|
|
const MODE_BLOCK = 'block';
|
|
const MODE_STRICT = 'strict';
|
|
|
|
protected static $defaults = array(
|
|
|
|
'mode' => self::MODE_MONITOR,
|
|
'waf_enabled' => true,
|
|
'login_protection_enabled' => true,
|
|
'xmlrpc_block_pingback' => true,
|
|
'rest_api_protection_enabled'=> true,
|
|
'integrity_scan_enabled' => true,
|
|
'malware_scan_enabled' => true,
|
|
'upload_scan_enabled' => true,
|
|
'vuln_intel_enabled' => true,
|
|
'geoip_rir_enabled' => true,
|
|
'auto_update_all_enabled' => true,
|
|
'static_cache_enabled' => false,
|
|
'static_cache_ttl_secs' => 3600,
|
|
'cache_stale_grace_secs' => 600,
|
|
'cache_custom_exclude_paths' => array(),
|
|
'cache_gzip_enabled' => true,
|
|
'cache_gzip_level' => 6,
|
|
'cache_brotli_enabled' => true,
|
|
'cache_brotli_level' => 5,
|
|
'cache_min_compress_bytes' => 1024,
|
|
'cache_discovery_enabled' => true,
|
|
'cache_warming_enabled' => false,
|
|
'cache_warm_concurrency' => 4,
|
|
'cache_warm_batch_size' => 20,
|
|
'cache_warm_min_interval_secs' => 1,
|
|
'challenge_enabled' => true,
|
|
'login_attempt_threshold' => 5,
|
|
'login_attempt_window_secs' => 600,
|
|
'login_lockout_secs' => 900,
|
|
'exceptions' => array(),
|
|
'cloud_connected' => false,
|
|
|
|
// Off by default -- ANIS Cloud shares blocked visitors' IP addresses
|
|
// with an external service, so it requires the site owner's explicit
|
|
// opt-in (Settings checkbox or the first-run admin notice) rather than
|
|
// connecting silently on activation. WordPress.org's guidelines
|
|
// require clear consent before any such external connection; this is
|
|
// still zero-configuration in the sense that mattered before -- there
|
|
// is no server address, license key, or tier field to fill in, only
|
|
// a single "enable" action.
|
|
'anis_enabled' => false,
|
|
'anis_base_url' => 'https://anis.weboria.eu',
|
|
'anis_license_key' => '',
|
|
|
|
'backup_schedule_mode' => 'automatic',
|
|
'backup_interval_hours' => 24,
|
|
'backup_retention_count' => 5,
|
|
);
|
|
|
|
public static function all() {
|
|
$stored = get_option( self::OPTION, array() );
|
|
return wp_parse_args( $stored, self::$defaults );
|
|
}
|
|
|
|
public static function get( $key, $fallback = null ) {
|
|
$all = self::all();
|
|
return array_key_exists( $key, $all ) ? $all[ $key ] : $fallback;
|
|
}
|
|
|
|
public static function update( array $partial ) {
|
|
$all = array_merge( self::all(), $partial );
|
|
|
|
update_option( self::OPTION, $all, true );
|
|
return $all;
|
|
}
|
|
|
|
public static function mode() {
|
|
return self::get( 'mode', self::MODE_MONITOR );
|
|
}
|
|
|
|
public static function is_monitor_only() {
|
|
return self::mode() === self::MODE_MONITOR;
|
|
}
|
|
|
|
public static function is_strict() {
|
|
return self::mode() === self::MODE_STRICT;
|
|
}
|
|
|
|
public static function has_exception( $type, $value ) {
|
|
foreach ( self::get( 'exceptions', array() ) as $exception ) {
|
|
if ( ( $exception['type'] ?? '' ) === $type && ( $exception['value'] ?? '' ) === $value ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static function add_exception( $type, $value, $note = '' ) {
|
|
if ( self::has_exception( $type, $value ) ) {
|
|
return self::all();
|
|
}
|
|
$exceptions = self::get( 'exceptions', array() );
|
|
$exceptions[] = array(
|
|
'type' => $type,
|
|
'value' => $value,
|
|
'note' => $note,
|
|
'added_at' => current_time( 'mysql', true ),
|
|
);
|
|
return self::update( array( 'exceptions' => $exceptions ) );
|
|
}
|
|
|
|
public static function remove_exception( $type, $value ) {
|
|
$exceptions = array_values(
|
|
array_filter(
|
|
self::get( 'exceptions', array() ),
|
|
function ( $e ) use ( $type, $value ) {
|
|
return ! ( ( $e['type'] ?? '' ) === $type && ( $e['value'] ?? '' ) === $value );
|
|
}
|
|
)
|
|
);
|
|
return self::update( array( 'exceptions' => $exceptions ) );
|
|
}
|
|
}
|