ARGUS WordPress Defence 7.23.0
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>
This commit is contained in:
@@ -4,14 +4,14 @@ Automatic WordPress security. A local firewall, malware and file-integrity scann
|
|||||||
vulnerability protection, and scheduled backups — protecting your site with zero manual
|
vulnerability protection, and scheduled backups — protecting your site with zero manual
|
||||||
configuration required.
|
configuration required.
|
||||||
|
|
||||||
**Version:** 1.0.0
|
**Version:** 7.23.0
|
||||||
**Requires:** WordPress 6.0+, PHP 7.4+
|
**Requires:** WordPress 6.0+, PHP 7.4+
|
||||||
**License:** GPLv2 or later
|
**License:** GPLv2 or later
|
||||||
|
|
||||||
## What it does
|
## What it does
|
||||||
|
|
||||||
- **Firewall** — blocks malicious requests (SQL injection, cross-site scripting, and more)
|
- **Firewall** — blocks malicious requests (SQL injection, cross-site scripting, remote code
|
||||||
before they reach your site.
|
execution, XXE, SSRF, and more) before they reach your site.
|
||||||
- **Scanner** — regularly checks WordPress core, plugins, and themes for suspicious files
|
- **Scanner** — regularly checks WordPress core, plugins, and themes for suspicious files
|
||||||
and integrity changes, and quarantines confirmed threats instead of just reporting them.
|
and integrity changes, and quarantines confirmed threats instead of just reporting them.
|
||||||
- **Vulnerability Protection** — checks your installed plugins, themes, and WordPress core
|
- **Vulnerability Protection** — checks your installed plugins, themes, and WordPress core
|
||||||
@@ -20,14 +20,21 @@ configuration required.
|
|||||||
outside the web root.
|
outside the web root.
|
||||||
- **Cache & Performance** — an optional page cache that can make your site faster, built
|
- **Cache & Performance** — an optional page cache that can make your site faster, built
|
||||||
to never interfere with the firewall or bans.
|
to never interfere with the firewall or bans.
|
||||||
- **Global Threat Intelligence** — automatically connected, no setup required.
|
- **Automatic Updates** — ARGUS always keeps itself up to date, and can optionally do the
|
||||||
|
same for every other installed plugin and theme.
|
||||||
|
- **Global Threat Intelligence (optional)** — share reputation data about the IP addresses
|
||||||
|
ARGUS blocks with a global network, and benefit from what every other connected site has
|
||||||
|
already seen. Off by default — turning it on is a single click, no fields to fill in, and
|
||||||
|
its own settings page shows exactly what is and isn't shared.
|
||||||
|
|
||||||
Everything works out of the box. There is nothing to configure to get protected.
|
Everything except Global Threat Intelligence works out of the box. There is nothing to
|
||||||
|
configure to get protected.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
This is a self-distributed release: it is not listed on WordPress.org and has not gone
|
This is a self-distributed release: it is not listed on WordPress.org and has not gone
|
||||||
through that team's plugin review process.
|
through that team's plugin review process (a submission is planned for the future — this
|
||||||
|
release is not it).
|
||||||
|
|
||||||
- **Not independently security audited.** It has been reviewed internally (code review,
|
- **Not independently security audited.** It has been reviewed internally (code review,
|
||||||
static checks, a manual security pass over authentication/authorization/nonce/SQL
|
static checks, a manual security pass over authentication/authorization/nonce/SQL
|
||||||
@@ -36,9 +43,11 @@ through that team's plugin review process.
|
|||||||
- **Tested in sandbox/disposable WordPress environments.** Verification so far — install,
|
- **Tested in sandbox/disposable WordPress environments.** Verification so far — install,
|
||||||
activation, all admin pages, deactivation, uninstall — was done on disposable WordPress
|
activation, all admin pages, deactivation, uninstall — was done on disposable WordPress
|
||||||
instances, not on live production sites.
|
instances, not on live production sites.
|
||||||
- **Automatic updates are not currently active.** The signed-update mechanism is built in
|
- **Automatic updates for ARGUS itself are not currently active.** The signed-update
|
||||||
but requires production update infrastructure (a manifest server and signing key) that
|
mechanism is built in but requires production update infrastructure (a manifest server
|
||||||
is not yet deployed. See [Updating](#updating) below for how to update manually.
|
and signing key) that is not yet deployed. See [Updating](#updating) below for how to
|
||||||
|
update manually. Automatic updates for *other* plugins/themes on your site, if you enable
|
||||||
|
that option, use WordPress's own built-in update system and work today.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -47,9 +56,10 @@ through that team's plugin review process.
|
|||||||
ZIP, then **Install Now**.
|
ZIP, then **Install Now**.
|
||||||
3. Click **Activate**.
|
3. Click **Activate**.
|
||||||
|
|
||||||
That's it — ARGUS Defence begins protecting your site automatically. A new **ARGUS
|
That's it — ARGUS Defence begins protecting your site automatically. A short Welcome screen
|
||||||
Defence** menu appears in your wp-admin sidebar with an overview of your site's
|
walks through what's already active and a couple of optional choices (a site-type template,
|
||||||
protection status.
|
Global Threat Intelligence). A new **ARGUS Defence** menu also appears in your wp-admin
|
||||||
|
sidebar with an overview of your site's protection status.
|
||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,28 @@ class Argus_Admin {
|
|||||||
add_action( 'admin_post_argus_wpd_download_backup', array( __CLASS__, 'handle_backup_download' ) );
|
add_action( 'admin_post_argus_wpd_download_backup', array( __CLASS__, 'handle_backup_download' ) );
|
||||||
|
|
||||||
add_action( 'admin_init', array( __CLASS__, 'handle_firewall_actions' ) );
|
add_action( 'admin_init', array( __CLASS__, 'handle_firewall_actions' ) );
|
||||||
|
add_action( 'admin_init', array( __CLASS__, 'maybe_redirect_to_welcome' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard WordPress plugin convention: redirect to a one-time Welcome
|
||||||
|
// screen right after activation, but never on a bulk-activate or a
|
||||||
|
// multisite network-wide activation (both would otherwise hijack
|
||||||
|
// whichever admin page the user was actually trying to reach).
|
||||||
|
public static function maybe_redirect_to_welcome() {
|
||||||
|
if ( ! get_transient( 'argus_wpd_do_activation_redirect' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete_transient( 'argus_wpd_do_activation_redirect' );
|
||||||
|
|
||||||
|
if ( wp_doing_ajax() || is_network_admin() || isset( $_GET['activate-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_safe_redirect( admin_url( 'admin.php?page=argus-wpd-welcome' ) );
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function handle_firewall_actions() {
|
public static function handle_firewall_actions() {
|
||||||
@@ -84,10 +106,57 @@ class Argus_Admin {
|
|||||||
add_submenu_page( 'argus-wpd-dashboard', __( 'ANIS', 'argus-wordpress-defence' ), __( 'ANIS', 'argus-wordpress-defence' ), 'manage_options', 'argus-wpd-anis', array( __CLASS__, 'render_anis' ) );
|
add_submenu_page( 'argus-wpd-dashboard', __( 'ANIS', 'argus-wordpress-defence' ), __( 'ANIS', 'argus-wordpress-defence' ), 'manage_options', 'argus-wpd-anis', array( __CLASS__, 'render_anis' ) );
|
||||||
add_submenu_page( 'argus-wpd-dashboard', __( 'Audit Log', 'argus-wordpress-defence' ), __( 'Audit Log', 'argus-wordpress-defence' ), 'manage_options', 'argus-wpd-audit-log', array( __CLASS__, 'render_audit_log' ) );
|
add_submenu_page( 'argus-wpd-dashboard', __( 'Audit Log', 'argus-wordpress-defence' ), __( 'Audit Log', 'argus-wordpress-defence' ), 'manage_options', 'argus-wpd-audit-log', array( __CLASS__, 'render_audit_log' ) );
|
||||||
add_submenu_page( 'argus-wpd-dashboard', __( 'Settings', 'argus-wordpress-defence' ), __( 'Settings', 'argus-wordpress-defence' ), 'manage_options', 'argus-wpd-settings', array( __CLASS__, 'render_settings' ) );
|
add_submenu_page( 'argus-wpd-dashboard', __( 'Settings', 'argus-wordpress-defence' ), __( 'Settings', 'argus-wordpress-defence' ), 'manage_options', 'argus-wpd-settings', array( __CLASS__, 'render_settings' ) );
|
||||||
|
|
||||||
|
// Reachable at ?page=argus-wpd-welcome right after activation, but
|
||||||
|
// CSS-hidden from the sidebar (hide_settings_from_sidebar(), same
|
||||||
|
// mechanism already used for Settings) -- it's a onboarding screen,
|
||||||
|
// not a place to keep coming back to via the nav. Registering with a
|
||||||
|
// real parent rather than null: passing null as parent_slug left
|
||||||
|
// get_admin_page_title() unable to resolve a title in this WP
|
||||||
|
// version, cascading into a "headers already sent" fatal-adjacent
|
||||||
|
// warning -- confirmed live, not theoretical.
|
||||||
|
add_submenu_page( 'argus-wpd-dashboard', __( 'Welcome to ARGUS Defence', 'argus-wordpress-defence' ), __( 'Welcome', 'argus-wordpress-defence' ), 'manage_options', 'argus-wpd-welcome', array( __CLASS__, 'render_welcome' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function render_welcome() {
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
wp_die( esc_html__( 'You do not have permission to do this.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$template_applied = null;
|
||||||
|
if ( isset( $_POST['argus_wpd_apply_template_nonce'], $_POST['template'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_apply_template_nonce'] ) ), 'argus_wpd_apply_template' ) ) { // phpcs:ignore
|
||||||
|
$key = sanitize_key( wp_unslash( $_POST['template'] ) ); // phpcs:ignore
|
||||||
|
if ( class_exists( 'Argus_Templates' ) && Argus_Templates::apply( $key ) ) {
|
||||||
|
$templates = Argus_Templates::all();
|
||||||
|
$template_applied = $templates[ $key ]['label'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $_POST['argus_wpd_anis_toggle_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_anis_toggle_nonce'] ) ), 'argus_wpd_anis_toggle' ) ) { // phpcs:ignore
|
||||||
|
$action = sanitize_key( wp_unslash( $_POST['anis_action'] ?? '' ) ); // phpcs:ignore
|
||||||
|
$user = wp_get_current_user();
|
||||||
|
if ( 'enable' === $action ) {
|
||||||
|
Argus_Settings::update( array( 'anis_enabled' => true ) );
|
||||||
|
Argus_ANIS_Client::register();
|
||||||
|
Argus_Events::record( 'anis_enabled', 'info', sprintf( 'ARGUS Cloud connection enabled by %s', $user->user_login ), array( 'actor' => $user->user_login ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $_POST['argus_wpd_auto_update_all_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_auto_update_all_nonce'] ) ), 'argus_wpd_save_auto_update_all' ) ) { // phpcs:ignore
|
||||||
|
Argus_Settings::update( array( 'auto_update_all_enabled' => ! empty( $_POST['auto_update_all_enabled'] ) ) ); // phpcs:ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = Argus_Settings::all();
|
||||||
|
$templates = class_exists( 'Argus_Templates' ) ? Argus_Templates::all() : array();
|
||||||
|
$active_template = class_exists( 'Argus_Templates' ) ? Argus_Templates::active() : '';
|
||||||
|
$recommended_template = class_exists( 'Argus_Templates' ) ? Argus_Templates::recommended() : null;
|
||||||
|
$anis_status = Argus_ANIS_Client::status();
|
||||||
|
|
||||||
|
include ARGUS_WPD_DIR . 'admin/views/welcome.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function hide_settings_from_sidebar() {
|
public static function hide_settings_from_sidebar() {
|
||||||
echo '<style>#adminmenu .wp-submenu a[href$="page=argus-wpd-settings"]{display:none}</style>';
|
echo '<style>#adminmenu .wp-submenu a[href$="page=argus-wpd-settings"],#adminmenu .wp-submenu a[href$="page=argus-wpd-welcome"]{display:none}</style>';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function enqueue_assets( $hook ) {
|
public static function enqueue_assets( $hook ) {
|
||||||
@@ -121,6 +190,15 @@ class Argus_Admin {
|
|||||||
$integrity_open = Argus_Findings::count_open( 'integrity' );
|
$integrity_open = Argus_Findings::count_open( 'integrity' );
|
||||||
$anis_status = Argus_ANIS_Client::status();
|
$anis_status = Argus_ANIS_Client::status();
|
||||||
|
|
||||||
|
$ips_tracked = Argus_ANIS_Client::local_reputation_count();
|
||||||
|
$anis_protections = Argus_ANIS_Client::blocked_count();
|
||||||
|
|
||||||
|
// Argus_License exists only in the separately-distributed Premium
|
||||||
|
// build (see includes/class-argus-license.php's own header) -- the
|
||||||
|
// free WordPress.org build has no trial/license concept at all, and
|
||||||
|
// this stays null there so overview.php shows nothing for it.
|
||||||
|
$license_summary = class_exists( 'Argus_License' ) ? Argus_License::summary() : null;
|
||||||
|
|
||||||
include ARGUS_WPD_DIR . 'admin/views/overview.php';
|
include ARGUS_WPD_DIR . 'admin/views/overview.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,6 +608,21 @@ class Argus_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function render_anis() {
|
public static function render_anis() {
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
wp_die( esc_html__( 'You do not have permission to do this.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
if ( isset( $_POST['argus_wpd_anis_toggle_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_anis_toggle_nonce'] ) ), 'argus_wpd_anis_toggle' ) ) { // phpcs:ignore
|
||||||
|
$action = sanitize_key( wp_unslash( $_POST['anis_action'] ?? '' ) ); // phpcs:ignore
|
||||||
|
$user = wp_get_current_user();
|
||||||
|
if ( 'enable' === $action ) {
|
||||||
|
Argus_Settings::update( array( 'anis_enabled' => true ) );
|
||||||
|
Argus_ANIS_Client::register();
|
||||||
|
Argus_Events::record( 'anis_enabled', 'info', sprintf( 'ARGUS Cloud connection enabled by %s', $user->user_login ), array( 'actor' => $user->user_login ) );
|
||||||
|
} elseif ( 'disable' === $action ) {
|
||||||
|
Argus_Settings::update( array( 'anis_enabled' => false ) );
|
||||||
|
Argus_Events::record( 'anis_disabled', 'info', sprintf( 'ARGUS Cloud connection disabled by %s', $user->user_login ), array( 'actor' => $user->user_login ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
$anis_status = Argus_ANIS_Client::status();
|
$anis_status = Argus_ANIS_Client::status();
|
||||||
include ARGUS_WPD_DIR . 'admin/views/anis.php';
|
include ARGUS_WPD_DIR . 'admin/views/anis.php';
|
||||||
}
|
}
|
||||||
@@ -582,6 +675,11 @@ class Argus_Admin {
|
|||||||
Argus_Settings::remove_exception( 'ip', sanitize_text_field( wp_unslash( $_POST['exception_value'] ) ) ); // phpcs:ignore
|
Argus_Settings::remove_exception( 'ip', sanitize_text_field( wp_unslash( $_POST['exception_value'] ) ) ); // phpcs:ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( isset( $_POST['argus_wpd_auto_update_all_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_auto_update_all_nonce'] ) ), 'argus_wpd_save_auto_update_all' ) ) { // phpcs:ignore
|
||||||
|
Argus_Settings::update( array( 'auto_update_all_enabled' => ! empty( $_POST['auto_update_all_enabled'] ) ) ); // phpcs:ignore
|
||||||
|
$saved = true;
|
||||||
|
}
|
||||||
|
|
||||||
$update_check_result = null;
|
$update_check_result = null;
|
||||||
if ( isset( $_POST['argus_wpd_check_updates_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_check_updates_nonce'] ) ), 'argus_wpd_check_updates' ) && class_exists( 'Argus_Update_Client' ) ) { // phpcs:ignore
|
if ( isset( $_POST['argus_wpd_check_updates_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_check_updates_nonce'] ) ), 'argus_wpd_check_updates' ) && class_exists( 'Argus_Update_Client' ) ) { // phpcs:ignore
|
||||||
$update_check_result = Argus_Update_Client::check_now();
|
$update_check_result = Argus_Update_Client::check_now();
|
||||||
|
|||||||
+29
-3
@@ -11,6 +11,7 @@ $protection_display = array(
|
|||||||
'active' => array( 'label' => __( 'ACTIVE', 'argus-wordpress-defence' ), 'chip' => 'chip-emerald' ),
|
'active' => array( 'label' => __( 'ACTIVE', 'argus-wordpress-defence' ), 'chip' => 'chip-emerald' ),
|
||||||
'limited' => array( 'label' => __( 'TEMPORARILY LIMITED', 'argus-wordpress-defence' ), 'chip' => 'chip-amber' ),
|
'limited' => array( 'label' => __( 'TEMPORARILY LIMITED', 'argus-wordpress-defence' ), 'chip' => 'chip-amber' ),
|
||||||
'not_connected' => array( 'label' => __( 'CONNECTING...', 'argus-wordpress-defence' ), 'chip' => 'chip-off' ),
|
'not_connected' => array( 'label' => __( 'CONNECTING...', 'argus-wordpress-defence' ), 'chip' => 'chip-off' ),
|
||||||
|
'disabled' => array( 'label' => __( 'NOT CONNECTED', 'argus-wordpress-defence' ), 'chip' => 'chip-off' ),
|
||||||
);
|
);
|
||||||
$state = $protection_display[ $anis_status['protection'] ];
|
$state = $protection_display[ $anis_status['protection'] ];
|
||||||
|
|
||||||
@@ -28,15 +29,33 @@ $next_sync_display = $anis_status['next_sync'] ? wp_date( $date_format, strtotim
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if ( 'disabled' === $anis_status['protection'] ) : ?>
|
||||||
|
|
||||||
|
<div class="argus-wpd-panel">
|
||||||
|
<p style="font-size:14px;color:var(--text);margin:0 0 16px;">
|
||||||
|
<?php esc_html_e( 'Your local protection (firewall, scanner, integrity, brute-force defense) is already fully active on its own. ARGUS Cloud is an optional upgrade: it shares reputation data about the IP addresses ARGUS blocks on your site with a global intelligence network, and in return your site benefits from what every other connected site has already seen.', 'argus-wordpress-defence' ); ?>
|
||||||
|
</p>
|
||||||
|
<p style="font-size:13px;color:var(--faint);margin:0 0 20px;line-height:1.7;">
|
||||||
|
<?php esc_html_e( 'What this shares, honestly: when ARGUS blocks a visitor on your site, the visitor\'s IP address, a short reason code, a confidence score, and (if resolvable) its country are sent to ARGUS Cloud. Nothing else -- never usernames, passwords, page content, or anything else about your site or its legitimate visitors. An IP address can be personal data, which is why this is off by default and only turns on if you choose to enable it.', 'argus-wordpress-defence' ); ?>
|
||||||
|
</p>
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field( 'argus_wpd_anis_toggle', 'argus_wpd_anis_toggle_nonce' ); ?>
|
||||||
|
<input type="hidden" name="anis_action" value="enable" />
|
||||||
|
<button type="submit" class="button button-primary"><?php esc_html_e( 'Connect to ARGUS Cloud', 'argus-wordpress-defence' ); ?></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php else : ?>
|
||||||
|
|
||||||
<div class="argus-wpd-panel">
|
<div class="argus-wpd-panel">
|
||||||
<p style="font-size:14px;color:var(--text);margin:0 0 20px;">
|
<p style="font-size:14px;color:var(--text);margin:0 0 20px;">
|
||||||
<?php
|
<?php
|
||||||
if ( 'active' === $anis_status['protection'] ) {
|
if ( 'active' === $anis_status['protection'] ) {
|
||||||
esc_html_e( 'ARGUS Defence is automatically protected by continuously updated global threat intelligence.', 'argus-wordpress-defence' );
|
esc_html_e( 'ARGUS Defence is connected to and protected by continuously updated global threat intelligence.', 'argus-wordpress-defence' );
|
||||||
} elseif ( 'limited' === $anis_status['protection'] ) {
|
} elseif ( 'limited' === $anis_status['protection'] ) {
|
||||||
esc_html_e( 'ARGUS Defence is continuing to protect your website while global threat intelligence connectivity is being restored automatically.', 'argus-wordpress-defence' );
|
esc_html_e( 'ARGUS Defence is continuing to protect your website while global threat intelligence connectivity is being restored automatically.', 'argus-wordpress-defence' );
|
||||||
} else {
|
} else {
|
||||||
esc_html_e( 'ARGUS Defence is automatically connecting to the global threat intelligence network. Your local protection is already fully active.', 'argus-wordpress-defence' );
|
esc_html_e( 'ARGUS Defence is connecting to the global threat intelligence network. Your local protection is already fully active.', 'argus-wordpress-defence' );
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</p>
|
</p>
|
||||||
@@ -68,7 +87,14 @@ $next_sync_display = $anis_status['next_sync'] ? wp_date( $date_format, strtotim
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="argus-wpd-panel">
|
<div class="argus-wpd-panel">
|
||||||
<p style="font-size:13px;color:var(--faint);margin:0;line-height:1.7;"><?php esc_html_e( 'Threat intelligence is privacy-focused. ARGUS Defence only shares anonymous security signals from activity already identified as malicious -- never usernames, passwords, page content, or anything that identifies your site or visitors.', 'argus-wordpress-defence' ); ?></p>
|
<p style="font-size:13px;color:var(--faint);margin:0 0 16px;line-height:1.7;"><?php esc_html_e( 'What this shares, honestly: when ARGUS blocks a visitor on your site, the visitor\'s IP address, a short reason code, a confidence score, and (if resolvable) its country are sent to ARGUS Cloud. Nothing else -- never usernames, passwords, page content, or anything else about your site or its legitimate visitors.', 'argus-wordpress-defence' ); ?></p>
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field( 'argus_wpd_anis_toggle', 'argus_wpd_anis_toggle_nonce' ); ?>
|
||||||
|
<input type="hidden" name="anis_action" value="disable" />
|
||||||
|
<button type="submit" class="button"><?php esc_html_e( 'Disconnect', 'argus-wordpress-defence' ); ?></button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php include ARGUS_WPD_DIR . 'admin/views/partials/footer.php'; ?>
|
<?php include ARGUS_WPD_DIR . 'admin/views/partials/footer.php'; ?>
|
||||||
|
|||||||
@@ -36,6 +36,34 @@ $score_offset = $score_circ * ( 1 - $score / 100 );
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ( null !== $license_summary ) : ?>
|
||||||
|
<div class="argus-wpd-panel" style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;">
|
||||||
|
<div>
|
||||||
|
<?php if ( 'licensed' === $license_summary['status'] ) : ?>
|
||||||
|
<span class="argus-wpd-status-pill" style="color:var(--emerald);border-color:rgba(63,185,80,.35);"><?php esc_html_e( 'LICENSED', 'argus-wordpress-defence' ); ?></span>
|
||||||
|
<span style="margin-left:10px;color:var(--faint);font-size:12.5px;"><?php esc_html_e( 'Premium features are fully active.', 'argus-wordpress-defence' ); ?></span>
|
||||||
|
<?php elseif ( 'trial' === $license_summary['status'] ) : ?>
|
||||||
|
<span class="argus-wpd-status-pill" style="color:var(--amber);border-color:rgba(255,170,0,.35);"><?php esc_html_e( 'TRIAL', 'argus-wordpress-defence' ); ?></span>
|
||||||
|
<span style="margin-left:10px;color:var(--faint);font-size:12.5px;">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %d: number of days remaining in the free trial */
|
||||||
|
esc_html( _n( '%d day left in your free trial -- everything is active.', '%d days left in your free trial -- everything is active.', $license_summary['days_remaining'], 'argus-wordpress-defence' ) ),
|
||||||
|
(int) $license_summary['days_remaining']
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="argus-wpd-status-pill" style="color:var(--rose);border-color:rgba(230,103,103,.35);"><?php esc_html_e( 'TRIAL ENDED', 'argus-wordpress-defence' ); ?></span>
|
||||||
|
<span style="margin-left:10px;color:var(--faint);font-size:12.5px;"><?php esc_html_e( 'Your 30-day free trial has ended. ARGUS keeps protecting your site -- enter a license key to keep Premium features and support.', 'argus-wordpress-defence' ); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php if ( 'licensed' !== $license_summary['status'] ) : ?>
|
||||||
|
<a class="button button-primary" href="<?php echo esc_url( $license_summary['purchase_url'] ? $license_summary['purchase_url'] : admin_url( 'admin.php?page=argus-wpd-settings' ) ); ?>"><?php echo $license_summary['purchase_url'] ? esc_html__( 'Buy a license', 'argus-wordpress-defence' ) : esc_html__( 'Enter license key', 'argus-wordpress-defence' ); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="argus-wpd-grid">
|
<div class="argus-wpd-grid">
|
||||||
<div class="argus-wpd-card argus-wpd-score">
|
<div class="argus-wpd-card argus-wpd-score">
|
||||||
<svg viewBox="0 0 128 128">
|
<svg viewBox="0 0 128 128">
|
||||||
@@ -71,6 +99,19 @@ $score_offset = $score_circ * ( 1 - $score / 100 );
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="argus-wpd-metric-grid argus-wpd-metric-grid-2">
|
||||||
|
<div class="argus-wpd-metric-card">
|
||||||
|
<div class="argus-wpd-metric-label"><?php esc_html_e( 'IPs Tracked', 'argus-wordpress-defence' ); ?></div>
|
||||||
|
<div class="argus-wpd-metric-value"><?php echo esc_html( number_format_i18n( $ips_tracked ) ); ?></div>
|
||||||
|
<div class="argus-wpd-metric-sub"><?php esc_html_e( 'Total IPs with threat intelligence on file, local + ANIS', 'argus-wordpress-defence' ); ?></div>
|
||||||
|
</div>
|
||||||
|
<div class="argus-wpd-metric-card">
|
||||||
|
<div class="argus-wpd-metric-label"><?php esc_html_e( 'ANIS Protections', 'argus-wordpress-defence' ); ?></div>
|
||||||
|
<div class="argus-wpd-metric-value"><?php echo esc_html( number_format_i18n( $anis_protections ) ); ?></div>
|
||||||
|
<div class="argus-wpd-metric-sub"><?php esc_html_e( 'Blocks powered by ARGUS global threat intelligence', 'argus-wordpress-defence' ); ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="argus-wpd-area-card">
|
<div class="argus-wpd-area-card">
|
||||||
<div class="argus-wpd-area-head">
|
<div class="argus-wpd-area-head">
|
||||||
<h2 style="margin:0"><?php esc_html_e( 'Blocked Requests', 'argus-wordpress-defence' ); ?> · <?php echo esc_html( $range_labels[ $range ] ?? $range ); ?></h2>
|
<h2 style="margin:0"><?php esc_html_e( 'Blocked Requests', 'argus-wordpress-defence' ); ?> · <?php echo esc_html( $range_labels[ $range ] ?? $range ); ?></h2>
|
||||||
@@ -103,6 +144,7 @@ $score_offset = $score_circ * ( 1 - $score / 100 );
|
|||||||
'active' => array( 'chip-cyan', __( 'CONNECTED', 'argus-wordpress-defence' ) ),
|
'active' => array( 'chip-cyan', __( 'CONNECTED', 'argus-wordpress-defence' ) ),
|
||||||
'limited' => array( 'chip-amber', __( 'LIMITED', 'argus-wordpress-defence' ) ),
|
'limited' => array( 'chip-amber', __( 'LIMITED', 'argus-wordpress-defence' ) ),
|
||||||
'not_connected' => array( 'chip-off', __( 'CONNECTING…', 'argus-wordpress-defence' ) ),
|
'not_connected' => array( 'chip-off', __( 'CONNECTING…', 'argus-wordpress-defence' ) ),
|
||||||
|
'disabled' => array( 'chip-off', __( 'NOT CONNECTED', 'argus-wordpress-defence' ) ),
|
||||||
);
|
);
|
||||||
list( $anis_chip_class, $anis_chip_state ) = $anis_chip_map[ $anis_status['protection'] ] ?? $anis_chip_map['not_connected'];
|
list( $anis_chip_class, $anis_chip_state ) = $anis_chip_map[ $anis_status['protection'] ] ?? $anis_chip_map['not_connected'];
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ $argus_nav_items = array(
|
|||||||
<?php echo esc_html( $argus_protection_display['label'] ); ?>
|
<?php echo esc_html( $argus_protection_display['label'] ); ?>
|
||||||
· <?php echo esc_html( $argus_mode_labels[ $argus_mode ] ?? $argus_mode ); ?>
|
· <?php echo esc_html( $argus_mode_labels[ $argus_mode ] ?? $argus_mode ); ?>
|
||||||
</div>
|
</div>
|
||||||
|
<span class="argus-wpd-version" title="<?php esc_attr_e( 'ARGUS WordPress Defence version', 'argus-wordpress-defence' ); ?>">v<?php echo esc_html( ARGUS_WPD_VERSION ); ?></span>
|
||||||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=argus-wpd-settings' ) ); ?>" class="argus-wpd-gear <?php echo 'settings' === $argus_active_page ? 'active' : ''; ?>" title="<?php esc_attr_e( 'Settings', 'argus-wordpress-defence' ); ?>">⚙</a>
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=argus-wpd-settings' ) ); ?>" class="argus-wpd-gear <?php echo 'settings' === $argus_active_page ? 'active' : ''; ?>" title="<?php esc_attr_e( 'Settings', 'argus-wordpress-defence' ); ?>">⚙</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<h1 class="argus-wpd-page-title"><?php esc_html_e( 'Settings', 'argus-wordpress-defence' ); ?></h1>
|
<h1 class="argus-wpd-page-title"><?php esc_html_e( 'Settings', 'argus-wordpress-defence' ); ?></h1>
|
||||||
<p style="color:var(--muted);font-size:13px;margin:0 0 24px;"><?php esc_html_e( 'Start in Monitor mode, review findings, then switch to Block once you trust legitimate traffic is not being flagged.', 'argus-wordpress-defence' ); ?></p>
|
<p style="color:var(--muted);font-size:13px;margin:0 0 24px;"><?php esc_html_e( 'Start in Monitor mode, review findings, then switch to Block once you trust legitimate traffic is not being flagged.', 'argus-wordpress-defence' ); ?> <a href="<?php echo esc_url( admin_url( 'admin.php?page=argus-wpd-welcome' ) ); ?>"><?php esc_html_e( 'Revisit the setup guide', 'argus-wordpress-defence' ); ?></a>.</p>
|
||||||
|
|
||||||
<?php if ( ! empty( $saved ) ) : ?>
|
<?php if ( ! empty( $saved ) ) : ?>
|
||||||
<div class="notice notice-success" style="padding:14px 18px;margin:0 0 20px;"><p style="margin:0"><?php esc_html_e( 'Settings saved.', 'argus-wordpress-defence' ); ?></p></div>
|
<div class="notice notice-success" style="padding:14px 18px;margin:0 0 20px;"><p style="margin:0"><?php esc_html_e( 'Settings saved.', 'argus-wordpress-defence' ); ?></p></div>
|
||||||
@@ -125,6 +125,12 @@ include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
|
|||||||
<h2><?php esc_html_e( 'Automatic Security Updates', 'argus-wordpress-defence' ); ?></h2>
|
<h2><?php esc_html_e( 'Automatic Security Updates', 'argus-wordpress-defence' ); ?></h2>
|
||||||
<p style="font-size:13px;color:var(--emerald);margin:0 0 6px;display:flex;align-items:center;gap:8px;"><span class="argus-wpd-status-chip chip-emerald" style="padding:2px 10px;"><span class="dot"></span><span class="state"><?php echo esc_html( strtoupper( Argus_Auto_Update::status()['label'] ) ); ?></span></span></p>
|
<p style="font-size:13px;color:var(--emerald);margin:0 0 6px;display:flex;align-items:center;gap:8px;"><span class="argus-wpd-status-chip chip-emerald" style="padding:2px 10px;"><span class="dot"></span><span class="state"><?php echo esc_html( strtoupper( Argus_Auto_Update::status()['label'] ) ); ?></span></span></p>
|
||||||
<p style="color:var(--faint);font-size:12.5px;margin:8px 0 0 0 0 16px;"><?php echo esc_html( Argus_Auto_Update::status()['detail'] ); ?></p>
|
<p style="color:var(--faint);font-size:12.5px;margin:8px 0 0 0 0 16px;"><?php echo esc_html( Argus_Auto_Update::status()['detail'] ); ?></p>
|
||||||
|
<form method="post" style="margin-top:14px;">
|
||||||
|
<?php wp_nonce_field( 'argus_wpd_save_auto_update_all', 'argus_wpd_auto_update_all_nonce' ); ?>
|
||||||
|
<label style="display:block;"><input type="checkbox" name="auto_update_all_enabled" <?php checked( $settings['auto_update_all_enabled'] ); ?> /> <?php esc_html_e( 'Also auto-update every other installed plugin and theme (recommended)', 'argus-wordpress-defence' ); ?></label>
|
||||||
|
<button type="submit" class="button" style="margin-top:10px;"><?php esc_html_e( 'Save', 'argus-wordpress-defence' ); ?></button>
|
||||||
|
</form>
|
||||||
|
<?php if ( class_exists( 'Argus_Update_Client' ) ) : ?>
|
||||||
<?php $update_status = Argus_Update_Client::status(); ?>
|
<?php $update_status = Argus_Update_Client::status(); ?>
|
||||||
<table class="widefat" style="border:none;background:transparent;margin-top:14px;">
|
<table class="widefat" style="border:none;background:transparent;margin-top:14px;">
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -171,6 +177,9 @@ include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
|
|||||||
<?php wp_nonce_field( 'argus_wpd_check_updates', 'argus_wpd_check_updates_nonce' ); ?>
|
<?php wp_nonce_field( 'argus_wpd_check_updates', 'argus_wpd_check_updates_nonce' ); ?>
|
||||||
<button type="submit" class="button"><?php esc_html_e( 'Check for Updates Now', 'argus-wordpress-defence' ); ?></button>
|
<button type="submit" class="button"><?php esc_html_e( 'Check for Updates Now', 'argus-wordpress-defence' ); ?></button>
|
||||||
</form>
|
</form>
|
||||||
|
<?php else : ?>
|
||||||
|
<p style="color:var(--faint);font-size:12.5px;margin:14px 0 0;"><?php esc_html_e( 'Updates for this plugin are delivered through WordPress.org, the same way as any other WordPress plugin -- you\'ll see a notification in your dashboard when a new version is available.', 'argus-wordpress-defence' ); ?></p>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="argus-wpd-panel">
|
<div class="argus-wpd-panel">
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$argus_active_page = 'welcome';
|
||||||
|
include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
|
||||||
|
|
||||||
|
$features = array(
|
||||||
|
array(
|
||||||
|
'title' => __( 'Firewall', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'Blocks malicious requests -- SQL injection, cross-site scripting, and dozens of other known attack patterns -- before they reach your site.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => $settings['waf_enabled'],
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( 'Malware Scanner & Quarantine', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'Watches for suspicious files, moves anything genuinely dangerous into a protected quarantine automatically, and lets you review or restore it.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => $settings['malware_scan_enabled'],
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( 'File Integrity Monitoring', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'Cross-checks WordPress core against official checksums and tracks plugin/theme file changes, so tampering doesn\'t go unnoticed.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => $settings['integrity_scan_enabled'],
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( 'Login & Brute-Force Protection', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'Detects and blocks repeated failed login attempts, and locks down XML-RPC/REST API endpoints commonly abused for credential-stuffing.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => $settings['login_protection_enabled'],
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( 'Vulnerability Intelligence', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'Checks WordPress core and every installed plugin/theme version against known security issues, so you know what needs updating and why.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => $settings['vuln_intel_enabled'],
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( 'Backups', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'Scheduled database and file backups, stored securely, so you have a real recovery point if something ever goes wrong.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => 'automatic' === $settings['backup_schedule_mode'],
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( 'Automatic Updates', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'ARGUS always keeps itself up to date, and can optionally do the same for every other plugin and theme -- unpatched software is one of the most common ways sites get compromised.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => $settings['auto_update_all_enabled'],
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( 'Cache & Performance', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'An optional page cache that can make your site noticeably faster -- it never bypasses the firewall or serves cached pages to already-blocked visitors.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => $settings['static_cache_enabled'],
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( 'ARGUS Cloud (Global Threat Intelligence)', 'argus-wordpress-defence' ),
|
||||||
|
'desc' => __( 'Optional: share reputation data about IPs ARGUS blocks with a global network, and benefit from what every other connected site has already seen.', 'argus-wordpress-defence' ),
|
||||||
|
'active' => $settings['anis_enabled'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="argus-wpd-welcome-hero">
|
||||||
|
<h1><?php esc_html_e( 'Welcome to ARGUS Defence', 'argus-wordpress-defence' ); ?></h1>
|
||||||
|
<p><?php esc_html_e( 'Your site is already protected -- ARGUS activated its core defenses the moment you installed it. Nothing below is required. It\'s here so you know exactly what\'s running, and to offer a couple of optional upgrades.', 'argus-wordpress-defence' ); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ( ! empty( $template_applied ) ) : ?>
|
||||||
|
<div class="notice notice-success" style="padding:14px 18px;margin:0 0 20px;"><p style="margin:0">
|
||||||
|
<?php printf( esc_html__( '"%s" protection template applied. You can fine-tune anything later in Settings.', 'argus-wordpress-defence' ), esc_html( $template_applied ) ); ?>
|
||||||
|
</p></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="argus-wpd-panel">
|
||||||
|
<h2><?php esc_html_e( 'What\'s protecting your site right now', 'argus-wordpress-defence' ); ?></h2>
|
||||||
|
<div class="argus-wpd-feature-grid">
|
||||||
|
<?php foreach ( $features as $feature ) : ?>
|
||||||
|
<div class="argus-wpd-feature-card">
|
||||||
|
<div class="argus-wpd-feature-card-head">
|
||||||
|
<span class="argus-wpd-feature-card-title"><?php echo esc_html( $feature['title'] ); ?></span>
|
||||||
|
<span class="argus-wpd-status-pill"><?php echo $feature['active'] ? esc_html__( 'ACTIVE', 'argus-wordpress-defence' ) : esc_html__( 'OFF', 'argus-wordpress-defence' ); ?></span>
|
||||||
|
</div>
|
||||||
|
<p class="argus-wpd-feature-card-desc"><?php echo esc_html( $feature['desc'] ); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ( ! empty( $templates ) ) : ?>
|
||||||
|
<div class="argus-wpd-panel">
|
||||||
|
<h2><?php esc_html_e( 'Optional: tailor it to your site', 'argus-wordpress-defence' ); ?></h2>
|
||||||
|
<p style="color:var(--faint);font-size:12.5px;margin:-8px 0 16px;"><?php esc_html_e( 'A starting point, not a lock-in -- applying a template sets sensible defaults, which you can hand-tune later in Settings.', 'argus-wordpress-defence' ); ?></p>
|
||||||
|
<div class="argus-wpd-template-grid">
|
||||||
|
<?php foreach ( $templates as $key => $template ) : ?>
|
||||||
|
<div class="argus-wpd-template-card<?php echo $active_template === $key ? ' active' : ''; ?>">
|
||||||
|
<div class="argus-wpd-template-title">
|
||||||
|
<?php echo esc_html( $template['label'] ); ?>
|
||||||
|
<?php if ( $recommended_template === $key ) : ?><span class="argus-wpd-template-badge"><?php esc_html_e( 'Recommended -- store plugin detected', 'argus-wordpress-defence' ); ?></span><?php endif; ?>
|
||||||
|
<?php if ( $active_template === $key ) : ?><span class="argus-wpd-template-badge active"><?php esc_html_e( 'Currently applied', 'argus-wordpress-defence' ); ?></span><?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<p class="argus-wpd-template-desc"><?php echo esc_html( $template['description'] ); ?></p>
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field( 'argus_wpd_apply_template', 'argus_wpd_apply_template_nonce' ); ?>
|
||||||
|
<input type="hidden" name="template" value="<?php echo esc_attr( $key ); ?>" />
|
||||||
|
<button type="submit" class="button<?php echo $active_template === $key ? '' : ' button-primary'; ?>"><?php echo $active_template === $key ? esc_html__( 'Re-apply', 'argus-wordpress-defence' ) : esc_html__( 'Apply', 'argus-wordpress-defence' ); ?></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="argus-wpd-panel">
|
||||||
|
<h2><?php esc_html_e( 'Optional: keep everything patched automatically', 'argus-wordpress-defence' ); ?></h2>
|
||||||
|
<p style="color:var(--faint);font-size:12.5px;margin:-8px 0 16px;"><?php esc_html_e( 'ARGUS always updates itself. Turning this on extends the same automatic updates to every other installed plugin and theme -- unpatched software is one of the most common ways WordPress sites get compromised.', 'argus-wordpress-defence' ); ?></p>
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field( 'argus_wpd_save_auto_update_all', 'argus_wpd_auto_update_all_nonce' ); ?>
|
||||||
|
<label style="display:block;"><input type="checkbox" name="auto_update_all_enabled" <?php checked( $settings['auto_update_all_enabled'] ); ?> /> <?php esc_html_e( 'Also auto-update every other installed plugin and theme (recommended)', 'argus-wordpress-defence' ); ?></label>
|
||||||
|
<button type="submit" class="button" style="margin-top:10px;"><?php esc_html_e( 'Save', 'argus-wordpress-defence' ); ?></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="argus-wpd-panel">
|
||||||
|
<h2><?php esc_html_e( 'Optional: connect to ARGUS Cloud', 'argus-wordpress-defence' ); ?></h2>
|
||||||
|
<?php if ( $settings['anis_enabled'] ) : ?>
|
||||||
|
<p style="font-size:13px;color:var(--emerald);margin:0;">✓ <?php esc_html_e( 'Connected. Manage this any time on the ANIS page.', 'argus-wordpress-defence' ); ?></p>
|
||||||
|
<?php else : ?>
|
||||||
|
<p style="font-size:13px;color:var(--faint);margin:0 0 16px;line-height:1.7;"><?php esc_html_e( 'Shares reputation data about IPs ARGUS blocks on your site with a global intelligence network, and benefits from what every other connected site has already seen. What this shares, honestly: the visitor\'s IP address, a short reason code, a confidence score, and (if resolvable) its country. Nothing else -- never usernames, passwords, or page content. Off by default because an IP address can be personal data; you can disconnect any time from the ANIS page.', 'argus-wordpress-defence' ); ?></p>
|
||||||
|
<form method="post">
|
||||||
|
<?php wp_nonce_field( 'argus_wpd_anis_toggle', 'argus_wpd_anis_toggle_nonce' ); ?>
|
||||||
|
<input type="hidden" name="anis_action" value="enable" />
|
||||||
|
<button type="submit" class="button button-primary"><?php esc_html_e( 'Connect to ARGUS Cloud', 'argus-wordpress-defence' ); ?></button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="argus-wpd-panel" style="text-align:center;">
|
||||||
|
<h2 style="margin-bottom:10px;"><?php esc_html_e( 'You\'re all set', 'argus-wordpress-defence' ); ?></h2>
|
||||||
|
<p style="color:var(--faint);font-size:13px;margin:0 0 18px;"><?php esc_html_e( 'ARGUS Defence is protecting your site. Come back to this checklist any time from Settings if you want to revisit these choices.', 'argus-wordpress-defence' ); ?></p>
|
||||||
|
<a class="button button-primary button-hero" href="<?php echo esc_url( admin_url( 'admin.php?page=argus-wpd-dashboard' ) ); ?>"><?php esc_html_e( 'Go to Dashboard', 'argus-wordpress-defence' ); ?></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include ARGUS_WPD_DIR . 'admin/views/partials/footer.php'; ?>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: ARGUS WordPress Defence
|
* Plugin Name: ARGUS WordPress Defence
|
||||||
* Plugin URI: https://git-cloud.weboria.eu/Weboria/argus-wp-defence
|
* Plugin URI: https://git-cloud.weboria.eu/Weboria/argus-wp-defence
|
||||||
* Description: Standalone WordPress security: local WAF, malware & integrity scanning, vulnerability intelligence, and a deterministic ban/policy engine. Works fully offline; optionally connects to ARGUS Cloud for richer intelligence and cross-asset correlation.
|
* Description: Standalone WordPress security: local WAF, malware & integrity scanning, vulnerability intelligence, and a deterministic ban/policy engine. Works fully offline; optionally connects to ARGUS Cloud for richer intelligence and cross-asset correlation.
|
||||||
* Version: 1.0.0
|
* Version: 7.23.0
|
||||||
* Requires at least: 6.0
|
* Requires at least: 6.0
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 7.4
|
||||||
* Author: ARGUS
|
* Author: ARGUS
|
||||||
@@ -28,7 +28,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define( 'ARGUS_WPD_VERSION', '1.0.0' );
|
define( 'ARGUS_WPD_VERSION', '7.23.0' );
|
||||||
define( 'ARGUS_WPD_FILE', __FILE__ );
|
define( 'ARGUS_WPD_FILE', __FILE__ );
|
||||||
define( 'ARGUS_WPD_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'ARGUS_WPD_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'ARGUS_WPD_URL', plugin_dir_url( __FILE__ ) );
|
define( 'ARGUS_WPD_URL', plugin_dir_url( __FILE__ ) );
|
||||||
|
|||||||
@@ -284,6 +284,26 @@
|
|||||||
.argus-wpd-donut-legend-value { font: 700 12px/1 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
|
.argus-wpd-donut-legend-value { font: 700 12px/1 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
|
||||||
.argus-wpd-donut-legend-value em { color: var(--faint); font-style: normal; margin-left: 3px; }
|
.argus-wpd-donut-legend-value em { color: var(--faint); font-style: normal; margin-left: 3px; }
|
||||||
|
|
||||||
|
/* ---------- Welcome / onboarding: feature explainer grid ---------- */
|
||||||
|
.argus-wpd-feature-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
|
||||||
|
@media (max-width: 900px) { .argus-wpd-feature-grid { grid-template-columns: 1fr; } }
|
||||||
|
.argus-wpd-feature-card {
|
||||||
|
background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 16px 18px;
|
||||||
|
}
|
||||||
|
.argus-wpd-feature-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 6px; }
|
||||||
|
.argus-wpd-feature-card-title { font: 700 13.5px/1.3 inherit; color: var(--text); }
|
||||||
|
.argus-wpd-feature-card-desc { font-size: 12.5px; color: var(--faint); line-height: 1.6; margin: 0; }
|
||||||
|
.argus-wpd-welcome-hero { text-align: center; padding: 8px 0 28px; }
|
||||||
|
.argus-wpd-welcome-hero h1 { font-size: 24px; margin: 0 0 8px; }
|
||||||
|
.argus-wpd-welcome-hero p { font-size: 14px; color: var(--muted); max-width: 560px; margin: 0 auto; }
|
||||||
|
|
||||||
|
/* ---------- Console: version tag ---------- */
|
||||||
|
.argus-wpd-version {
|
||||||
|
margin-left: 10px; flex-shrink: 0; font: 600 11px/1 var(--mono); letter-spacing: .02em;
|
||||||
|
color: var(--faint); padding: 6px 10px; border-radius: 9px;
|
||||||
|
background: var(--panel); border: 1px solid var(--border-strong);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Console: gear icon (Settings, off primary nav) ---------- */
|
/* ---------- Console: gear icon (Settings, off primary nav) ---------- */
|
||||||
.argus-wpd-gear {
|
.argus-wpd-gear {
|
||||||
margin-left: 10px; width: 38px; height: 38px; border-radius: 9px; flex-shrink: 0;
|
margin-left: 10px; width: 38px; height: 38px; border-radius: 9px; flex-shrink: 0;
|
||||||
@@ -295,6 +315,7 @@
|
|||||||
|
|
||||||
/* ---------- Console: metric cards w/ trend ---------- */
|
/* ---------- Console: metric cards w/ trend ---------- */
|
||||||
.argus-wpd-metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 20px; }
|
.argus-wpd-metric-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 20px; }
|
||||||
|
.argus-wpd-metric-grid.argus-wpd-metric-grid-2 { grid-template-columns: repeat(2, 1fr); }
|
||||||
@media (max-width: 1100px) { .argus-wpd-metric-grid { grid-template-columns: 1fr 1fr; } }
|
@media (max-width: 1100px) { .argus-wpd-metric-grid { grid-template-columns: 1fr 1fr; } }
|
||||||
@media (max-width: 640px) { .argus-wpd-metric-grid { grid-template-columns: 1fr; } }
|
@media (max-width: 640px) { .argus-wpd-metric-grid { grid-template-columns: 1fr; } }
|
||||||
.argus-wpd-metric-card {
|
.argus-wpd-metric-card {
|
||||||
|
|||||||
@@ -31,9 +31,26 @@ rm -rf \
|
|||||||
"${STAGE_DIR}/tests" \
|
"${STAGE_DIR}/tests" \
|
||||||
"${STAGE_DIR}/README.md" \
|
"${STAGE_DIR}/README.md" \
|
||||||
"${STAGE_DIR}/ARGUS_WORDPRESS_SECURITY_ARCHITECTURE.md" \
|
"${STAGE_DIR}/ARGUS_WORDPRESS_SECURITY_ARCHITECTURE.md" \
|
||||||
|
"${STAGE_DIR}/WORDPRESS_ORG_READINESS_AUDIT.md" \
|
||||||
"${STAGE_DIR}/.gitignore" \
|
"${STAGE_DIR}/.gitignore" \
|
||||||
"${STAGE_DIR}/bin"
|
"${STAGE_DIR}/bin"
|
||||||
|
|
||||||
|
# A plugin hosted on WordPress.org must rely solely on WordPress.org's own
|
||||||
|
# update channel -- never a self-update-from-external-manifest mechanism,
|
||||||
|
# even one that ships inert-by-default. class_exists( 'Argus_Update_Client' )
|
||||||
|
# guards every reference to this class elsewhere in the codebase, so removing
|
||||||
|
# the file here is sufficient; nothing else needs to change per build.
|
||||||
|
echo "==> Removing the self-update client (WordPress.org must be the only update channel)"
|
||||||
|
rm -f "${STAGE_DIR}/includes/class-argus-update-client.php"
|
||||||
|
|
||||||
|
# WordPress.org explicitly prohibits trialware -- a plugin submitted to the
|
||||||
|
# directory must stay genuinely, permanently functional with no license
|
||||||
|
# requirement. class_exists( 'Argus_License' ) guards every reference to
|
||||||
|
# this class elsewhere, so removing the file here is sufficient; the free
|
||||||
|
# build never has a trial timer at all.
|
||||||
|
echo "==> Removing the license/trial system (WordPress.org prohibits trialware)"
|
||||||
|
rm -f "${STAGE_DIR}/includes/class-argus-license.php"
|
||||||
|
|
||||||
echo "==> Allowlist audit -- fail closed on anything unexpected"
|
echo "==> Allowlist audit -- fail closed on anything unexpected"
|
||||||
UNEXPECTED=0
|
UNEXPECTED=0
|
||||||
while IFS= read -r -d '' item; do
|
while IFS= read -r -d '' item; do
|
||||||
|
|||||||
@@ -36,6 +36,20 @@ class Argus_Activator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_option( 'argus_wpd_activated_at', current_time( 'mysql', true ), false );
|
update_option( 'argus_wpd_activated_at', current_time( 'mysql', true ), false );
|
||||||
|
|
||||||
|
// Argus_License exists only in the separately-distributed Premium
|
||||||
|
// build (excluded from the WordPress.org release, see
|
||||||
|
// bin/build-release.sh) -- the free build has no trial timer at all.
|
||||||
|
if ( class_exists( 'Argus_License' ) ) {
|
||||||
|
Argus_License::ensure_trial_started();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to the Welcome screen on the very next admin_init --
|
||||||
|
// Argus_Admin::maybe_redirect_to_welcome() is the one that decides
|
||||||
|
// whether this was a real single-plugin activation vs. a bulk-activate
|
||||||
|
// or network-wide multisite activation (neither of which should
|
||||||
|
// hijack the admin's next page load), and clears this either way.
|
||||||
|
set_transient( 'argus_wpd_do_activation_redirect', 1, MINUTE_IN_SECONDS );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function schedule_cron() {
|
protected static function schedule_cron() {
|
||||||
|
|||||||
@@ -558,7 +558,7 @@ class Argus_ANIS_Client {
|
|||||||
|
|
||||||
return array(
|
return array(
|
||||||
'configured' => self::is_configured(),
|
'configured' => self::is_configured(),
|
||||||
'enabled' => (bool) Argus_Settings::get( 'anis_enabled', true ),
|
'enabled' => (bool) Argus_Settings::get( 'anis_enabled', false ),
|
||||||
'connected' => self::is_connected(),
|
'connected' => self::is_connected(),
|
||||||
'status' => get_option( self::STATUS_OPTION, 'unconfigured' ),
|
'status' => get_option( self::STATUS_OPTION, 'unconfigured' ),
|
||||||
'last_error' => get_option( self::LAST_ERROR_OPTION, '' ),
|
'last_error' => get_option( self::LAST_ERROR_OPTION, '' ),
|
||||||
@@ -574,6 +574,9 @@ class Argus_ANIS_Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static function protection_state() {
|
protected static function protection_state() {
|
||||||
|
if ( ! (bool) Argus_Settings::get( 'anis_enabled', false ) ) {
|
||||||
|
return 'disabled';
|
||||||
|
}
|
||||||
if ( ! self::is_enabled() ) {
|
if ( ! self::is_enabled() ) {
|
||||||
return 'not_connected';
|
return 'not_connected';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,38 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
class Argus_Auto_Update {
|
class Argus_Auto_Update {
|
||||||
|
|
||||||
public static function init() {
|
public static function init() {
|
||||||
add_filter( 'auto_update_plugin', array( __CLASS__, 'force_auto_update' ), 10, 2 );
|
add_filter( 'auto_update_plugin', array( __CLASS__, 'force_plugin_auto_update' ), 10, 2 );
|
||||||
|
add_filter( 'auto_update_theme', array( __CLASS__, 'force_theme_auto_update' ), 10, 2 );
|
||||||
add_filter( 'plugin_auto_update_setting_html', array( __CLASS__, 'lock_admin_toggle' ), 10, 3 );
|
add_filter( 'plugin_auto_update_setting_html', array( __CLASS__, 'lock_admin_toggle' ), 10, 3 );
|
||||||
add_filter( 'site_option_auto_update_plugins', array( __CLASS__, 'ensure_in_list' ) );
|
add_filter( 'site_option_auto_update_plugins', array( __CLASS__, 'ensure_self_in_list' ) );
|
||||||
add_filter( 'option_auto_update_plugins', array( __CLASS__, 'ensure_in_list' ) );
|
add_filter( 'option_auto_update_plugins', array( __CLASS__, 'ensure_self_in_list' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function force_auto_update( $update, $item ) {
|
// ARGUS always auto-updates itself, independent of the site-wide setting
|
||||||
|
// below -- this is not user-configurable, matching the plugin's own
|
||||||
|
// self-protection design (ADR-0053 SS13.1).
|
||||||
|
public static function force_plugin_auto_update( $update, $item ) {
|
||||||
if ( isset( $item->plugin ) && ARGUS_WPD_BASENAME === $item->plugin ) {
|
if ( isset( $item->plugin ) && ARGUS_WPD_BASENAME === $item->plugin ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if ( Argus_Settings::get( 'auto_update_all_enabled', true ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return $update;
|
return $update;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function ensure_in_list( $list ) {
|
// Uses WordPress core's own native automatic-update system
|
||||||
|
// (WP_Automatic_Updater, driven by the existing wp_version_check/
|
||||||
|
// wp_update_themes cron events and the official WordPress.org Themes
|
||||||
|
// API) -- no custom download/replace code, nothing external.
|
||||||
|
public static function force_theme_auto_update( $update, $item ) {
|
||||||
|
if ( Argus_Settings::get( 'auto_update_all_enabled', true ) ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return $update;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function ensure_self_in_list( $list ) {
|
||||||
$list = is_array( $list ) ? $list : array();
|
$list = is_array( $list ) ? $list : array();
|
||||||
if ( ! in_array( ARGUS_WPD_BASENAME, $list, true ) ) {
|
if ( ! in_array( ARGUS_WPD_BASENAME, $list, true ) ) {
|
||||||
$list[] = ARGUS_WPD_BASENAME;
|
$list[] = ARGUS_WPD_BASENAME;
|
||||||
@@ -37,9 +55,15 @@ class Argus_Auto_Update {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function status() {
|
public static function status() {
|
||||||
|
if ( Argus_Settings::get( 'auto_update_all_enabled', true ) ) {
|
||||||
|
return array(
|
||||||
|
'label' => __( 'Enabled for All Plugins & Themes', 'argus-wordpress-defence' ),
|
||||||
|
'detail' => __( 'ARGUS Defence keeps ARGUS itself, and every other installed plugin and theme, on their latest available version automatically -- unpatched plugins/themes are one of the most common ways WordPress sites get compromised. You can turn this off for everything except ARGUS itself below.', 'argus-wordpress-defence' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
return array(
|
return array(
|
||||||
'label' => __( 'Enabled and Protected', 'argus-wordpress-defence' ),
|
'label' => __( 'Enabled for ARGUS Only', 'argus-wordpress-defence' ),
|
||||||
'detail' => __( 'ARGUS Defence automatically installs important security, protection, stability, and compatibility updates to help keep your website protected. This setting is managed automatically and cannot be disabled from the plugin.', 'argus-wordpress-defence' ),
|
'detail' => __( 'ARGUS Defence automatically installs its own updates to stay protected. Automatic updates for your other plugins and themes are off -- turn them on below for stronger baseline protection.', 'argus-wordpress-defence' ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -404,6 +404,11 @@ class Argus_Console_Stats {
|
|||||||
'rce' => __( 'Remote Code Execution', 'argus-wordpress-defence' ),
|
'rce' => __( 'Remote Code Execution', 'argus-wordpress-defence' ),
|
||||||
'file_access' => __( 'Path Traversal / LFI', 'argus-wordpress-defence' ),
|
'file_access' => __( 'Path Traversal / LFI', 'argus-wordpress-defence' ),
|
||||||
'protocol_anomaly' => __( 'Protocol Anomaly', 'argus-wordpress-defence' ),
|
'protocol_anomaly' => __( 'Protocol Anomaly', 'argus-wordpress-defence' ),
|
||||||
|
'xxe' => __( 'XML External Entity', 'argus-wordpress-defence' ),
|
||||||
|
'ssrf' => __( 'Server-Side Request Forgery', 'argus-wordpress-defence' ),
|
||||||
|
'session_fixation' => __( 'Session Fixation', 'argus-wordpress-defence' ),
|
||||||
|
'java_injection' => __( 'Java / JNDI Injection', 'argus-wordpress-defence' ),
|
||||||
|
'scanner_activity' => __( 'Scanner / Recon Activity', 'argus-wordpress-defence' ),
|
||||||
);
|
);
|
||||||
return $labels[ $category ] ?? ucwords( str_replace( '_', ' ', $category ) );
|
return $labels[ $category ] ?? ucwords( str_replace( '_', ' ', $category ) );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,36 +141,68 @@ class Argus_Malware_Scanner {
|
|||||||
$rel = str_replace( wp_normalize_path( ABSPATH ), '', wp_normalize_path( $path ) );
|
$rel = str_replace( wp_normalize_path( ABSPATH ), '', wp_normalize_path( $path ) );
|
||||||
|
|
||||||
if ( self::is_restored_trusted( $rel, hash_file( 'sha256', $path ) ) ) {
|
if ( self::is_restored_trusted( $rel, hash_file( 'sha256', $path ) ) ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$size = filesize( $path );
|
$size = filesize( $path );
|
||||||
$mtime = gmdate( 'Y-m-d H:i:s', filemtime( $path ) );
|
$mtime = gmdate( 'Y-m-d H:i:s', filemtime( $path ) );
|
||||||
|
|
||||||
|
// The detection rule itself ("a PHP file exists inside uploads") is
|
||||||
|
// location-based and unconditional -- WordPress never executes PHP
|
||||||
|
// there by design, so any match is quarantined regardless of content.
|
||||||
|
// The content heuristic score is a SEPARATE signal layered on top,
|
||||||
|
// used only to set an honest severity/verdict -- never to skip
|
||||||
|
// quarantining. This keeps "rule matched" (always true here),
|
||||||
|
// "analysis score" (score_content()'s real number), and "verdict"
|
||||||
|
// (the severity below) from contradicting each other in the UI, e.g.
|
||||||
|
// a benign defensive stub scoring 0 must never be labeled CRITICAL.
|
||||||
|
$content = is_readable( $path ) ? file_get_contents( $path ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||||
|
list( $content_score, $matched ) = false !== $content ? self::score_content( $content ) : array( 0, array() );
|
||||||
|
|
||||||
|
if ( $content_score >= self::THRESHOLD_HIGH ) {
|
||||||
|
$final_severity = 'critical';
|
||||||
|
$verdict = 'Its content strongly matches known webshell/backdoor patterns.';
|
||||||
|
} elseif ( $content_score >= self::THRESHOLD_MEDIUM ) {
|
||||||
|
$final_severity = 'high';
|
||||||
|
$verdict = 'Its content contains some patterns also seen in malicious files, though less conclusively.';
|
||||||
|
} else {
|
||||||
|
$final_severity = 'medium';
|
||||||
|
$verdict = 'No suspicious code patterns were found in its content -- it has been quarantined as a precaution because a PHP file in uploads is itself unusual, not because of what it contains.';
|
||||||
|
}
|
||||||
|
|
||||||
$result = Argus_Quarantine::quarantine_file(
|
$result = Argus_Quarantine::quarantine_file(
|
||||||
$path,
|
$path,
|
||||||
$rel,
|
$rel,
|
||||||
array(
|
array(
|
||||||
'engine' => 'Argus_Malware_Scanner',
|
'engine' => 'Argus_Malware_Scanner',
|
||||||
'rule' => 'php-in-uploads',
|
'rule' => 'php-in-uploads',
|
||||||
'type' => 'signature',
|
'type' => 'signature',
|
||||||
'severity' => 'critical',
|
'severity' => $final_severity,
|
||||||
|
'content_score' => $content_score,
|
||||||
|
'matched_rules' => wp_list_pluck( $matched, 'id' ),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$neutralized = false !== $result;
|
$neutralized = false !== $result;
|
||||||
|
|
||||||
$finding_id = Argus_Findings::record(
|
$finding_id = Argus_Findings::record(
|
||||||
'malware',
|
'malware',
|
||||||
'critical',
|
$final_severity,
|
||||||
array(
|
array(
|
||||||
'what_happened' => 'A PHP file was found inside the uploads directory: ' . $rel,
|
'what_happened' => 'A PHP file was found inside the uploads directory: ' . $rel,
|
||||||
'why_it_matters' => 'WordPress does not execute PHP inside wp-content/uploads by design -- a legitimate install should never have PHP files there.',
|
'why_it_matters' => 'WordPress does not execute PHP inside wp-content/uploads by design -- a legitimate install should never have PHP files there.',
|
||||||
'what_argus_found' => sprintf( 'File: %s, size %s bytes, last modified %s.', $rel, $size, $mtime ),
|
'what_argus_found' => sprintf(
|
||||||
|
'File: %1$s, size %2$s bytes, last modified %3$s. Detection rule "php-in-uploads" matched (location-based, always fires on any .php file here). Content analysis score: %4$d/100%5$s.',
|
||||||
|
$rel,
|
||||||
|
$size,
|
||||||
|
$mtime,
|
||||||
|
$content_score,
|
||||||
|
$matched ? ' (matched: ' . implode( ', ', wp_list_pluck( $matched, 'id' ) ) . ')' : ' (no heuristic patterns matched)'
|
||||||
|
),
|
||||||
'when_it_happened' => current_time( 'mysql' ),
|
'when_it_happened' => current_time( 'mysql' ),
|
||||||
'why_suspicious' => 'This is one of the most common webshell placement techniques -- uploads directories are usually writable by the web application, unlike wp-admin/wp-includes.',
|
'why_suspicious' => 'This is one of the most common webshell placement techniques -- uploads directories are usually writable by the web application, unlike wp-admin/wp-includes. ' . $verdict,
|
||||||
'what_could_be_affected' => 'A PHP file here can be executed directly by visiting its URL, potentially giving an attacker full code execution on your server.',
|
'what_could_be_affected' => 'A PHP file here can be executed directly by visiting its URL, potentially giving an attacker full code execution on your server.',
|
||||||
'what_should_you_do' => $neutralized
|
'what_should_you_do' => $neutralized
|
||||||
? 'This file has been quarantined -- moved to a protected, non-web-accessible store. Review it on the Quarantine page and decide whether to restore or permanently delete it.'
|
? 'This file has been quarantined -- moved to a protected, non-web-accessible store. Review it on the Quarantine page (its content analysis score is shown there) and decide whether to restore or permanently delete it.'
|
||||||
: 'ARGUS detected this file but could not quarantine it (the uploads directory is not writable by PHP on this host, or it is a symlink ARGUS refuses to move automatically). Remove it manually and investigate how it got there.',
|
: 'ARGUS detected this file but could not quarantine it (the uploads directory is not writable by PHP on this host, or it is a symlink ARGUS refuses to move automatically). Remove it manually and investigate how it got there.',
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
@@ -178,6 +210,7 @@ class Argus_Malware_Scanner {
|
|||||||
'file_path' => $rel,
|
'file_path' => $rel,
|
||||||
'file_mtime' => $mtime,
|
'file_mtime' => $mtime,
|
||||||
'neutralized' => $neutralized,
|
'neutralized' => $neutralized,
|
||||||
|
'content_score' => $content_score,
|
||||||
),
|
),
|
||||||
Argus_Findings::STATUS_OPEN
|
Argus_Findings::STATUS_OPEN
|
||||||
);
|
);
|
||||||
@@ -196,7 +229,8 @@ class Argus_Malware_Scanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static function scan_recently_changed() {
|
protected static function scan_recently_changed() {
|
||||||
$since = (int) get_option( self::SCAN_WATERMARK_OPTION, time() - HOUR_IN_SECONDS );
|
$since = (int) get_option( self::SCAN_WATERMARK_OPTION, time() - HOUR_IN_SECONDS );
|
||||||
|
$own_dir = wp_normalize_path( ARGUS_WPD_DIR );
|
||||||
|
|
||||||
foreach ( array( WP_PLUGIN_DIR, get_theme_root(), ABSPATH . 'wp-admin', ABSPATH . WPINC ) as $dir ) {
|
foreach ( array( WP_PLUGIN_DIR, get_theme_root(), ABSPATH . 'wp-admin', ABSPATH . WPINC ) as $dir ) {
|
||||||
if ( ! is_dir( $dir ) ) {
|
if ( ! is_dir( $dir ) ) {
|
||||||
@@ -210,6 +244,21 @@ class Argus_Malware_Scanner {
|
|||||||
if ( ! $file->isFile() || 'php' !== strtolower( $file->getExtension() ) ) {
|
if ( ! $file->isFile() || 'php' !== strtolower( $file->getExtension() ) ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$path = wp_normalize_path( $file->getPathname() );
|
||||||
|
|
||||||
|
// ARGUS never treats its own installed files as a finding about
|
||||||
|
// themselves -- its test fixtures legitimately contain literal
|
||||||
|
// attack-pattern strings (e.g. eval(base64_decode(...)) samples),
|
||||||
|
// and its own WAF/scanner rule definitions legitimately contain
|
||||||
|
// the same keywords as regex source. Genuine tampering with
|
||||||
|
// ARGUS's own code is a different threat model (an already-
|
||||||
|
// compromised server) that a self-scan wouldn't meaningfully
|
||||||
|
// catch anyway. Its own supply-chain integrity is covered by
|
||||||
|
// WordPress.org's review/signing and its own forced self-update.
|
||||||
|
if ( 0 === strpos( $path, $own_dir ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ( $file->getMTime() < $since ) {
|
if ( $file->getMTime() < $since ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ class Argus_Plugin {
|
|||||||
Argus_DB::maybe_upgrade();
|
Argus_DB::maybe_upgrade();
|
||||||
Argus_MU_Installer::ensure_current();
|
Argus_MU_Installer::ensure_current();
|
||||||
|
|
||||||
|
// Premium-build-only (see includes/class-argus-license.php's own
|
||||||
|
// header) -- covers sites that were already active before this
|
||||||
|
// existed, same idempotent-ensure pattern as Argus_MU_Installer above.
|
||||||
|
if ( class_exists( 'Argus_License' ) ) {
|
||||||
|
Argus_License::ensure_trial_started();
|
||||||
|
}
|
||||||
|
|
||||||
add_filter( 'cron_schedules', array( __CLASS__, 'register_cron_schedules' ) ); // phpcs:ignore WordPress.WP.CronInterval
|
add_filter( 'cron_schedules', array( __CLASS__, 'register_cron_schedules' ) ); // phpcs:ignore WordPress.WP.CronInterval
|
||||||
|
|
||||||
Argus_Login_Guard::init();
|
Argus_Login_Guard::init();
|
||||||
@@ -37,7 +44,16 @@ class Argus_Plugin {
|
|||||||
add_action( 'argus_wpd_five_minutes', array( 'Argus_ANIS_Client', 'maybe_retry_sync' ) );
|
add_action( 'argus_wpd_five_minutes', array( 'Argus_ANIS_Client', 'maybe_retry_sync' ) );
|
||||||
add_action( 'argus_wpd_five_minutes', array( 'Argus_Vuln_Intel', 'maybe_retry' ) );
|
add_action( 'argus_wpd_five_minutes', array( 'Argus_Vuln_Intel', 'maybe_retry' ) );
|
||||||
|
|
||||||
add_action( 'argus_wpd_five_minutes', array( 'Argus_Update_Client', 'maybe_check' ) );
|
// Argus_Update_Client is deliberately excluded from the WordPress.org-
|
||||||
|
// distributed build (bin/build-release.sh) -- a plugin hosted on
|
||||||
|
// WordPress.org must rely solely on WordPress.org's own update
|
||||||
|
// channel, never a self-update-from-external-manifest mechanism, even
|
||||||
|
// an inert-by-default one. class_exists() guards it everywhere it's
|
||||||
|
// referenced so the rest of the plugin degrades cleanly when the file
|
||||||
|
// is absent, rather than the packaging step needing to also edit code.
|
||||||
|
if ( class_exists( 'Argus_Update_Client' ) ) {
|
||||||
|
add_action( 'argus_wpd_five_minutes', array( 'Argus_Update_Client', 'maybe_check' ) );
|
||||||
|
}
|
||||||
|
|
||||||
if ( class_exists( 'Argus_Integrity' ) ) {
|
if ( class_exists( 'Argus_Integrity' ) ) {
|
||||||
add_action( 'upgrader_process_complete', array( 'Argus_Integrity', 'on_upgrader_complete' ), 10, 2 );
|
add_action( 'upgrader_process_complete', array( 'Argus_Integrity', 'on_upgrader_complete' ), 10, 2 );
|
||||||
|
|||||||
@@ -88,6 +88,15 @@ class Argus_Quarantine {
|
|||||||
|
|
||||||
$now = current_time( 'mysql', true );
|
$now = current_time( 'mysql', true );
|
||||||
|
|
||||||
|
// Detection engines that already ran a content-based heuristic score
|
||||||
|
// (e.g. Argus_Malware_Scanner::score_content()) can pass it through here
|
||||||
|
// so the quarantine row never sits with a severity label and no
|
||||||
|
// supporting analysis data behind it -- 'severity' is the rule's
|
||||||
|
// verdict, 'content_score'/'matched_rules' are the raw evidence for it,
|
||||||
|
// and they must always be inserted together, not one now and one later
|
||||||
|
// via a separate manual Analyse click.
|
||||||
|
$has_content_score = isset( $detection['content_score'] );
|
||||||
|
|
||||||
$wpdb->insert(
|
$wpdb->insert(
|
||||||
Argus_DB::table( 'quarantine' ),
|
Argus_DB::table( 'quarantine' ),
|
||||||
array(
|
array(
|
||||||
@@ -102,10 +111,13 @@ class Argus_Quarantine {
|
|||||||
'detection_rule' => $detection['rule'] ?? null,
|
'detection_rule' => $detection['rule'] ?? null,
|
||||||
'detection_type' => $detection['type'],
|
'detection_type' => $detection['type'],
|
||||||
'severity' => $detection['severity'],
|
'severity' => $detection['severity'],
|
||||||
|
'confidence_score' => $has_content_score ? (int) $detection['content_score'] : null,
|
||||||
|
'matched_rules' => ! empty( $detection['matched_rules'] ) ? wp_json_encode( $detection['matched_rules'] ) : null,
|
||||||
|
'analysed_at' => $has_content_score ? $now : null,
|
||||||
'status' => self::STATUS_QUARANTINED,
|
'status' => self::STATUS_QUARANTINED,
|
||||||
'quarantined_at' => $now,
|
'quarantined_at' => $now,
|
||||||
),
|
),
|
||||||
array( '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
|
array( '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s' )
|
||||||
);
|
);
|
||||||
|
|
||||||
$id = (int) $wpdb->insert_id;
|
$id = (int) $wpdb->insert_id;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class Argus_Settings {
|
|||||||
'upload_scan_enabled' => true,
|
'upload_scan_enabled' => true,
|
||||||
'vuln_intel_enabled' => true,
|
'vuln_intel_enabled' => true,
|
||||||
'geoip_rir_enabled' => true,
|
'geoip_rir_enabled' => true,
|
||||||
|
'auto_update_all_enabled' => true,
|
||||||
'static_cache_enabled' => false,
|
'static_cache_enabled' => false,
|
||||||
'static_cache_ttl_secs' => 3600,
|
'static_cache_ttl_secs' => 3600,
|
||||||
'cache_stale_grace_secs' => 600,
|
'cache_stale_grace_secs' => 600,
|
||||||
@@ -45,7 +46,15 @@ class Argus_Settings {
|
|||||||
'exceptions' => array(),
|
'exceptions' => array(),
|
||||||
'cloud_connected' => false,
|
'cloud_connected' => false,
|
||||||
|
|
||||||
'anis_enabled' => true,
|
// 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_base_url' => 'https://anis.weboria.eu',
|
||||||
'anis_license_key' => '',
|
'anis_license_key' => '',
|
||||||
|
|
||||||
|
|||||||
@@ -1,272 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
if ( ! defined( 'ABSPATH' ) ) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Argus_Update_Client {
|
|
||||||
|
|
||||||
const CHECK_INTERVAL_SECS = 6 * HOUR_IN_SECONDS;
|
|
||||||
const LOCK_TRANSIENT = 'argus_wpd_update_check_lock';
|
|
||||||
|
|
||||||
const LAST_CHECK_OPTION = 'argus_wpd_update_last_check';
|
|
||||||
const MANIFEST_ETAG_OPTION = 'argus_wpd_update_manifest_etag';
|
|
||||||
const PENDING_MANIFEST_OPTION = 'argus_wpd_update_pending_manifest';
|
|
||||||
const LAST_SUCCESS_OPTION = 'argus_wpd_update_last_success';
|
|
||||||
const LAST_FAILURE_OPTION = 'argus_wpd_update_last_failure';
|
|
||||||
|
|
||||||
public static function manifest_url() {
|
|
||||||
return defined( 'ARGUS_WPD_UPDATE_MANIFEST_URL' ) ? ARGUS_WPD_UPDATE_MANIFEST_URL : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function public_key() {
|
|
||||||
return defined( 'ARGUS_WPD_UPDATE_PUBLIC_KEY' ) ? ARGUS_WPD_UPDATE_PUBLIC_KEY : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function is_configured() {
|
|
||||||
return '' !== self::manifest_url() && '' !== self::public_key();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function maybe_check() {
|
|
||||||
if ( ! self::is_configured() ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$last_check = (int) get_option( self::LAST_CHECK_OPTION, 0 );
|
|
||||||
if ( ( $last_check + self::CHECK_INTERVAL_SECS ) > time() ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( false !== get_transient( self::LOCK_TRANSIENT ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
set_transient( self::LOCK_TRANSIENT, 1, 4 * MINUTE_IN_SECONDS );
|
|
||||||
|
|
||||||
self::check_now();
|
|
||||||
|
|
||||||
delete_transient( self::LOCK_TRANSIENT );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function check_now() {
|
|
||||||
if ( ! self::is_configured() ) {
|
|
||||||
return array( 'success' => false, 'message' => __( 'No update source is configured.', 'argus-wordpress-defence' ), 'update_available' => false );
|
|
||||||
}
|
|
||||||
|
|
||||||
update_option( self::LAST_CHECK_OPTION, time(), false );
|
|
||||||
|
|
||||||
$args = array( 'timeout' => 15 );
|
|
||||||
$etag = get_option( self::MANIFEST_ETAG_OPTION, '' );
|
|
||||||
if ( $etag ) {
|
|
||||||
$args['headers'] = array( 'If-None-Match' => $etag );
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = wp_remote_get( self::manifest_url(), $args );
|
|
||||||
if ( is_wp_error( $response ) ) {
|
|
||||||
return self::record_failure( __( 'Could not reach the update server.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$code = wp_remote_retrieve_response_code( $response );
|
|
||||||
if ( 304 === $code ) {
|
|
||||||
|
|
||||||
return array( 'success' => true, 'message' => '', 'update_available' => false );
|
|
||||||
}
|
|
||||||
if ( 200 !== $code ) {
|
|
||||||
return self::record_failure( sprintf( 'Update server returned HTTP %d.', $code ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$new_etag = wp_remote_retrieve_header( $response, 'etag' );
|
|
||||||
if ( $new_etag ) {
|
|
||||||
update_option( self::MANIFEST_ETAG_OPTION, $new_etag, false );
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = wp_remote_retrieve_body( $response );
|
|
||||||
$manifest = json_decode( $body, true );
|
|
||||||
if ( ! is_array( $manifest ) ) {
|
|
||||||
return self::record_failure( __( 'Update manifest was not valid JSON.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::process_manifest( $manifest );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function process_manifest( array $manifest ) {
|
|
||||||
|
|
||||||
if ( ! self::verify_signature( $manifest ) ) {
|
|
||||||
return self::record_failure( __( 'Update manifest failed signature verification.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$version = $manifest['version'] ?? '';
|
|
||||||
if ( '' === $version ) {
|
|
||||||
return self::record_failure( __( 'Update manifest is missing a version.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( version_compare( $version, ARGUS_WPD_VERSION, '<=' ) ) {
|
|
||||||
return array( 'success' => true, 'message' => '', 'update_available' => false );
|
|
||||||
}
|
|
||||||
|
|
||||||
$last_installed = get_option( self::LAST_SUCCESS_OPTION, array() );
|
|
||||||
if ( ! empty( $last_installed['version'] ) && version_compare( $version, $last_installed['version'], '<=' ) ) {
|
|
||||||
return array( 'success' => true, 'message' => '', 'update_available' => false );
|
|
||||||
}
|
|
||||||
|
|
||||||
global $wp_version;
|
|
||||||
if ( ! empty( $manifest['min_php'] ) && version_compare( PHP_VERSION, $manifest['min_php'], '<' ) ) {
|
|
||||||
return self::record_failure( sprintf( 'Update %s requires PHP %s or newer.', $version, $manifest['min_php'] ) );
|
|
||||||
}
|
|
||||||
if ( ! empty( $manifest['min_wp'] ) && version_compare( $wp_version, $manifest['min_wp'], '<' ) ) {
|
|
||||||
return self::record_failure( sprintf( 'Update %s requires WordPress %s or newer.', $version, $manifest['min_wp'] ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
update_option( self::PENDING_MANIFEST_OPTION, $manifest, false );
|
|
||||||
|
|
||||||
if ( ! empty( $manifest['critical'] ) ) {
|
|
||||||
|
|
||||||
return self::install_update( $manifest );
|
|
||||||
}
|
|
||||||
|
|
||||||
return array( 'success' => true, 'message' => '', 'update_available' => true );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function verify_signature( array $manifest ) {
|
|
||||||
if ( empty( $manifest['signature'] ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$signature = base64_decode( $manifest['signature'], true );
|
|
||||||
if ( false === $signature || SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$public_key_b64 = self::public_key();
|
|
||||||
if ( '' === $public_key_b64 ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$public_key = base64_decode( $public_key_b64, true );
|
|
||||||
if ( false === $public_key ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$canonical = self::canonical_payload( $manifest );
|
|
||||||
return sodium_crypto_sign_verify_detached( $signature, $canonical, $public_key );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function canonical_payload( array $manifest ) {
|
|
||||||
$ordered = array();
|
|
||||||
foreach ( array( 'version', 'released_at', 'package_url', 'sha256', 'min_php', 'min_wp', 'critical' ) as $key ) {
|
|
||||||
$ordered[ $key ] = $manifest[ $key ] ?? null;
|
|
||||||
}
|
|
||||||
return wp_json_encode( $ordered, JSON_UNESCAPED_SLASHES );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function install_update( array $manifest, $target_dir = null ) {
|
|
||||||
$target_dir = $target_dir ?: ( WP_PLUGIN_DIR . '/argus-wordpress-defence' );
|
|
||||||
$version = $manifest['version'];
|
|
||||||
|
|
||||||
$upgrade_dir = trailingslashit( wp_get_upload_dir()['basedir'] ) . 'argus-wpd-data/update-staging';
|
|
||||||
if ( ! is_dir( $upgrade_dir ) && ! wp_mkdir_p( $upgrade_dir ) ) {
|
|
||||||
return self::record_failure( __( 'Could not prepare the update staging directory.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$package_path = trailingslashit( $upgrade_dir ) . 'package-' . $version . '-' . bin2hex( random_bytes( 4 ) ) . '.zip';
|
|
||||||
$response = wp_remote_get( $manifest['package_url'], array( 'timeout' => 120, 'stream' => true, 'filename' => $package_path ) );
|
|
||||||
if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
|
|
||||||
@unlink( $package_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors, WordPress.WP.AlternativeFunctions
|
|
||||||
return self::record_failure( __( 'Could not download the update package.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! hash_equals( $manifest['sha256'], hash_file( 'sha256', $package_path ) ) ) {
|
|
||||||
wp_delete_file( $package_path );
|
|
||||||
return self::record_failure( __( 'Downloaded package hash did not match the signed manifest -- rejected.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$extract_root = trailingslashit( $upgrade_dir ) . 'extract-' . $version . '-' . bin2hex( random_bytes( 4 ) );
|
|
||||||
if ( ! class_exists( 'ZipArchive' ) ) {
|
|
||||||
wp_delete_file( $package_path );
|
|
||||||
return self::record_failure( __( 'The PHP zip extension is not available -- cannot install the update.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
$zip = new ZipArchive();
|
|
||||||
if ( true !== $zip->open( $package_path ) || true !== $zip->extractTo( $extract_root ) ) {
|
|
||||||
wp_delete_file( $package_path );
|
|
||||||
return self::record_failure( __( 'Could not extract the update package.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
$zip->close();
|
|
||||||
wp_delete_file( $package_path );
|
|
||||||
|
|
||||||
$extracted_plugin_dir = trailingslashit( $extract_root ) . 'argus-wordpress-defence';
|
|
||||||
$new_main_file = trailingslashit( $extracted_plugin_dir ) . 'argus-wordpress-defence.php';
|
|
||||||
if ( ! file_exists( $new_main_file ) ) {
|
|
||||||
self::rrmdir( $extract_root );
|
|
||||||
return self::record_failure( __( 'Extracted package did not contain the expected plugin file.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$header_contents = file_get_contents( $new_main_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
|
||||||
if ( ! preg_match( '/Version:\s*([^\r\n]+)/', $header_contents, $m ) || trim( $m[1] ) !== $version ) {
|
|
||||||
self::rrmdir( $extract_root );
|
|
||||||
return self::record_failure( __( 'Extracted package version did not match the signed manifest -- rejected.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
$backup_dir = $target_dir . '-previous-' . time();
|
|
||||||
$had_previous = is_dir( $target_dir );
|
|
||||||
if ( $had_previous && ! rename( $target_dir, $backup_dir ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions
|
|
||||||
self::rrmdir( $extract_root );
|
|
||||||
return self::record_failure( __( 'Could not move the current plugin version aside for the update.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
if ( ! rename( $extracted_plugin_dir, $target_dir ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions
|
|
||||||
|
|
||||||
if ( $had_previous ) {
|
|
||||||
rename( $backup_dir, $target_dir ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
|
||||||
}
|
|
||||||
self::rrmdir( $extract_root );
|
|
||||||
return self::record_failure( __( 'Could not activate the downloaded update -- rolled back to the previous version.', 'argus-wordpress-defence' ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
self::rrmdir( $extract_root );
|
|
||||||
if ( $had_previous ) {
|
|
||||||
self::rrmdir( $backup_dir );
|
|
||||||
}
|
|
||||||
|
|
||||||
delete_option( self::PENDING_MANIFEST_OPTION );
|
|
||||||
update_option(
|
|
||||||
self::LAST_SUCCESS_OPTION,
|
|
||||||
array( 'version' => $version, 'installed_at' => current_time( 'mysql', true ) ),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'success' => true,
|
|
||||||
'message' => ! empty( $manifest['critical'] )
|
|
||||||
? sprintf( __( 'A critical ARGUS security update was installed (%s).', 'argus-wordpress-defence' ), $version )
|
|
||||||
: sprintf( __( 'ARGUS Defence was updated to version %s.', 'argus-wordpress-defence' ), $version ),
|
|
||||||
'update_available' => false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function record_failure( $message ) {
|
|
||||||
update_option(
|
|
||||||
self::LAST_FAILURE_OPTION,
|
|
||||||
array( 'message' => $message, 'at' => current_time( 'mysql', true ) ),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
Argus_Events::record( 'update_check_failed', 'medium', $message, array() );
|
|
||||||
return array( 'success' => false, 'message' => $message, 'update_available' => false );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function rrmdir( $dir ) {
|
|
||||||
if ( ! is_dir( $dir ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$items = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST );
|
|
||||||
foreach ( $items as $item ) {
|
|
||||||
$item->isDir() ? rmdir( $item->getPathname() ) : unlink( $item->getPathname() ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
|
||||||
}
|
|
||||||
rmdir( $dir ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function status() {
|
|
||||||
return array(
|
|
||||||
'current_version' => ARGUS_WPD_VERSION,
|
|
||||||
'configured' => self::is_configured(),
|
|
||||||
'last_check' => get_option( self::LAST_CHECK_OPTION, 0 ),
|
|
||||||
'last_success' => get_option( self::LAST_SUCCESS_OPTION, array() ),
|
|
||||||
'last_failure' => get_option( self::LAST_FAILURE_OPTION, array() ),
|
|
||||||
'pending' => get_option( self::PENDING_MANIFEST_OPTION, array() ),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,11 +11,12 @@ class Argus_WAF_Rules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$rules = array(
|
$rules = array(
|
||||||
|
|
||||||
|
// --- SQL Injection (CRS 942-XXX equivalent) -------------------------------------------------
|
||||||
|
|
||||||
array( 'id' => 'sqli-union-select', 'category' => 'sql_injection', 'severity' => 'critical',
|
array( 'id' => 'sqli-union-select', 'category' => 'sql_injection', 'severity' => 'critical',
|
||||||
'pattern' => '/\bunion\b[^\w]{1,20}\bselect\b/i' ),
|
'pattern' => '/\bunion\b[^\w]{1,20}\bselect\b/i' ),
|
||||||
array( 'id' => 'sqli-classic-tautology', 'category' => 'sql_injection', 'severity' => 'critical',
|
array( 'id' => 'sqli-classic-tautology', 'category' => 'sql_injection', 'severity' => 'critical',
|
||||||
|
|
||||||
'pattern' => '/(\%27|\'|\%22|")\s*(or|and)\s*(\%27|\'|\%22|")?[\d\w]+(\%27|\'|\%22|")?\s*=\s*(\%27|\'|\%22|")?[\d\w]+(\%27|\'|\%22|")?/i' ),
|
'pattern' => '/(\%27|\'|\%22|")\s*(or|and)\s*(\%27|\'|\%22|")?[\d\w]+(\%27|\'|\%22|")?\s*=\s*(\%27|\'|\%22|")?[\d\w]+(\%27|\'|\%22|")?/i' ),
|
||||||
array( 'id' => 'sqli-information-schema', 'category' => 'sql_injection', 'severity' => 'critical',
|
array( 'id' => 'sqli-information-schema', 'category' => 'sql_injection', 'severity' => 'critical',
|
||||||
'pattern' => '/information_schema|sysobjects|sysdatabases/i' ),
|
'pattern' => '/information_schema|sysobjects|sysdatabases/i' ),
|
||||||
@@ -23,41 +24,116 @@ class Argus_WAF_Rules {
|
|||||||
'pattern' => '/\b(sleep|benchmark|pg_sleep|waitfor\s+delay)\s*\(/i' ),
|
'pattern' => '/\b(sleep|benchmark|pg_sleep|waitfor\s+delay)\s*\(/i' ),
|
||||||
array( 'id' => 'sqli-stacked-comment', 'category' => 'sql_injection', 'severity' => 'high',
|
array( 'id' => 'sqli-stacked-comment', 'category' => 'sql_injection', 'severity' => 'high',
|
||||||
'pattern' => '/;\s*(drop|delete|update|insert)\s+(table|from|into)/i' ),
|
'pattern' => '/;\s*(drop|delete|update|insert)\s+(table|from|into)/i' ),
|
||||||
|
array( 'id' => 'sqli-error-based-xpath', 'category' => 'sql_injection', 'severity' => 'critical',
|
||||||
|
'pattern' => '/\b(extractvalue|updatexml)\s*\(/i' ),
|
||||||
|
array( 'id' => 'sqli-file-read-write', 'category' => 'sql_injection', 'severity' => 'critical',
|
||||||
|
'pattern' => '/\b(load_file|into\s+outfile|into\s+dumpfile)\s*\(?/i' ),
|
||||||
|
array( 'id' => 'sqli-mssql-xp-cmdshell', 'category' => 'sql_injection', 'severity' => 'critical',
|
||||||
|
'pattern' => '/\bxp_cmdshell\b/i' ),
|
||||||
|
array( 'id' => 'sqli-hex-literal', 'category' => 'sql_injection', 'severity' => 'medium',
|
||||||
|
'pattern' => '/\bunhex\s*\(|0x[0-9a-f]{12,}/i' ),
|
||||||
|
array( 'id' => 'sqli-conditional-error', 'category' => 'sql_injection', 'severity' => 'high',
|
||||||
|
'pattern' => '/\b(and|or)\s+\d+\s*=\s*\d+\s*(--|#|\/\*)/i' ),
|
||||||
|
|
||||||
|
// --- Cross-Site Scripting (CRS 941-XXX equivalent) -------------------------------------------------
|
||||||
|
|
||||||
array( 'id' => 'xss-script-tag', 'category' => 'xss', 'severity' => 'high',
|
array( 'id' => 'xss-script-tag', 'category' => 'xss', 'severity' => 'high',
|
||||||
'pattern' => '/<\s*script[\s>\/]/i' ),
|
'pattern' => '/<\s*script[\s>\/]/i' ),
|
||||||
|
|
||||||
array( 'id' => 'xss-event-handler', 'category' => 'xss', 'severity' => 'high',
|
array( 'id' => 'xss-event-handler', 'category' => 'xss', 'severity' => 'high',
|
||||||
'pattern' => '/\bon(error|load|mouseover|click|focus)\s*=\s*["\']?[^"\'>]*[\(\{]/i' ),
|
'pattern' => '/\bon(error|load|mouseover|click|focus)\s*=\s*["\']?[^"\'>]*[\(\{]/i' ),
|
||||||
array( 'id' => 'xss-javascript-uri', 'category' => 'xss', 'severity' => 'high',
|
array( 'id' => 'xss-javascript-uri', 'category' => 'xss', 'severity' => 'high',
|
||||||
'pattern' => '/javascript\s*:\s*[^\s]/i' ),
|
'pattern' => '/javascript\s*:\s*[^\s]/i' ),
|
||||||
array( 'id' => 'xss-svg-onload', 'category' => 'xss', 'severity' => 'high',
|
array( 'id' => 'xss-svg-onload', 'category' => 'xss', 'severity' => 'high',
|
||||||
'pattern' => '/<\s*svg[^>]*onload/i' ),
|
'pattern' => '/<\s*svg[^>]*onload/i' ),
|
||||||
|
array( 'id' => 'xss-embed-object-tag', 'category' => 'xss', 'severity' => 'high',
|
||||||
|
'pattern' => '/<\s*(iframe|object|embed)\b[^>]*(src|data)\s*=/i' ),
|
||||||
|
array( 'id' => 'xss-data-uri-html', 'category' => 'xss', 'severity' => 'high',
|
||||||
|
'pattern' => '/data\s*:\s*text\/html\s*;\s*base64/i' ),
|
||||||
|
array( 'id' => 'xss-vbscript-uri', 'category' => 'xss', 'severity' => 'high',
|
||||||
|
'pattern' => '/vbscript\s*:/i' ),
|
||||||
|
array( 'id' => 'xss-css-expression', 'category' => 'xss', 'severity' => 'medium',
|
||||||
|
'pattern' => '/style\s*=\s*["\'][^"\']*expression\s*\(/i' ),
|
||||||
|
array( 'id' => 'xss-dom-sink', 'category' => 'xss', 'severity' => 'medium',
|
||||||
|
'pattern' => '/document\s*\.\s*(cookie|write|location)\s*[=\(]|window\s*\.\s*location\s*=/i' ),
|
||||||
|
array( 'id' => 'xss-entity-encoded-script', 'category' => 'xss', 'severity' => 'medium',
|
||||||
|
'pattern' => '/&(lt|#0*60|#x3c)\s*;?\s*script/i' ),
|
||||||
|
|
||||||
|
// --- Remote Code Execution / Command Injection (CRS 932-XXX equivalent) -------------------------------------------------
|
||||||
|
|
||||||
array( 'id' => 'cmdi-shell-metachars', 'category' => 'rce', 'severity' => 'critical',
|
array( 'id' => 'cmdi-shell-metachars', 'category' => 'rce', 'severity' => 'critical',
|
||||||
'pattern' => '/;\s*(cat|ls|whoami|id|uname|wget|curl)\s/i' ),
|
'pattern' => '/;\s*(cat|ls|whoami|id|uname|wget|curl)\s/i' ),
|
||||||
array( 'id' => 'cmdi-backtick-subshell', 'category' => 'rce', 'severity' => 'critical',
|
array( 'id' => 'cmdi-backtick-subshell', 'category' => 'rce', 'severity' => 'critical',
|
||||||
'pattern' => '/`[^`]{1,80}`|\$\([^\)]{1,80}\)/' ),
|
'pattern' => '/`[^`]{1,80}`|\$\([^\)]{1,80}\)/' ),
|
||||||
|
|
||||||
|
// --- PHP Injection (CRS 933-XXX equivalent) -------------------------------------------------
|
||||||
|
|
||||||
array( 'id' => 'phpi-eval-base64', 'category' => 'rce', 'severity' => 'critical',
|
array( 'id' => 'phpi-eval-base64', 'category' => 'rce', 'severity' => 'critical',
|
||||||
'pattern' => '/\beval\s*\(\s*(base64_decode|gzinflate|str_rot13)\s*\(/i' ),
|
'pattern' => '/\beval\s*\(\s*(base64_decode|gzinflate|gzuncompress|str_rot13)\s*\(/i' ),
|
||||||
array( 'id' => 'phpi-dangerous-function', 'category' => 'rce', 'severity' => 'critical',
|
array( 'id' => 'phpi-dangerous-function', 'category' => 'rce', 'severity' => 'critical',
|
||||||
'pattern' => '/\b(system|exec|shell_exec|passthru|proc_open|popen)\s*\(/i' ),
|
'pattern' => '/\b(system|exec|shell_exec|passthru|proc_open|popen|pcntl_exec|dl)\s*\(/i' ),
|
||||||
|
array( 'id' => 'phpi-dynamic-eval-construct', 'category' => 'rce', 'severity' => 'critical',
|
||||||
|
'pattern' => '/\b(assert|create_function|call_user_func(_array)?)\s*\(/i' ),
|
||||||
|
array( 'id' => 'phpi-preg-replace-eval-modifier', 'category' => 'rce', 'severity' => 'critical',
|
||||||
|
'pattern' => '/preg_replace\s*\(\s*["\'][^"\']*\/[a-zA-Z]*e[a-zA-Z]*["\']/i' ),
|
||||||
array( 'id' => 'phpi-tag-in-input', 'category' => 'rce', 'severity' => 'high',
|
array( 'id' => 'phpi-tag-in-input', 'category' => 'rce', 'severity' => 'high',
|
||||||
'pattern' => '/<\?php|<\?=/i' ),
|
'pattern' => '/<\?php|<\?=/i' ),
|
||||||
|
|
||||||
|
// --- Java / JNDI Injection (CRS 944-XXX equivalent -- Log4Shell class) -------------------------------------------------
|
||||||
|
|
||||||
|
array( 'id' => 'java-jndi-lookup', 'category' => 'java_injection', 'severity' => 'critical',
|
||||||
|
'pattern' => '/\$\{jndi:(ldap|ldaps|rmi|dns|iiop|corba|nds|http|https):\/\//i' ),
|
||||||
|
array( 'id' => 'java-el-injection', 'category' => 'java_injection', 'severity' => 'high',
|
||||||
|
'pattern' => '/\$\{\s*(java|javax|org\.springframework|Runtime)\s*[\.\(]/' ),
|
||||||
|
|
||||||
|
// --- Local / Remote File Inclusion (CRS 930/931-XXX equivalent) -------------------------------------------------
|
||||||
|
|
||||||
array( 'id' => 'lfi-dot-dot-slash', 'category' => 'file_access', 'severity' => 'high',
|
array( 'id' => 'lfi-dot-dot-slash', 'category' => 'file_access', 'severity' => 'high',
|
||||||
'pattern' => '/(\.\.\/|\.\.\\\\|%2e%2e%2f|%252e%252e%252f)/i' ),
|
'pattern' => '/(\.\.\/|\.\.\\\\|%2e%2e%2f|%252e%252e%252f)/i' ),
|
||||||
array( 'id' => 'lfi-sensitive-file', 'category' => 'file_access', 'severity' => 'high',
|
array( 'id' => 'lfi-sensitive-file', 'category' => 'file_access', 'severity' => 'high',
|
||||||
'pattern' => '/\/etc\/(passwd|shadow|hosts)\b|wp-config\.php/i' ),
|
'pattern' => '/\/etc\/(passwd|shadow|hosts)\b|wp-config\.php/i' ),
|
||||||
array( 'id' => 'lfi-php-wrapper', 'category' => 'file_access', 'severity' => 'high',
|
array( 'id' => 'lfi-php-wrapper', 'category' => 'file_access', 'severity' => 'high',
|
||||||
'pattern' => '/php:\/\/(filter|input|data)/i' ),
|
'pattern' => '/(php|zip|phar|expect|glob)\s*:\/\/(filter|input|data|)/i' ),
|
||||||
array( 'id' => 'rfi-remote-scheme', 'category' => 'file_access', 'severity' => 'high',
|
array( 'id' => 'rfi-remote-scheme', 'category' => 'file_access', 'severity' => 'high',
|
||||||
'pattern' => '/^(https?|ftp):\/\/.+\.(php|txt)(\?|$)/i' ),
|
'pattern' => '/^(https?|ftp):\/\/.+\.(php|txt)(\?|$)/i' ),
|
||||||
|
array( 'id' => 'lfi-dotfile-path', 'category' => 'file_access', 'severity' => 'medium',
|
||||||
|
'pattern' => '/\/\.(git|svn|env|ssh|hg)\/[\w\-\.\/]*(config|credentials|id_rsa)?/i',
|
||||||
|
'sources' => array( 'HEADER:REQUEST_URI' ) ),
|
||||||
|
array( 'id' => 'lfi-backup-file-request', 'category' => 'file_access', 'severity' => 'medium',
|
||||||
|
'pattern' => '/\.(bak|old|swp|save|orig|sql|sql\.gz|tar\.gz)(\?|$)/i',
|
||||||
|
'sources' => array( 'HEADER:REQUEST_URI' ) ),
|
||||||
|
|
||||||
|
// --- XML External Entity (CRS-equivalent) -------------------------------------------------
|
||||||
|
|
||||||
|
array( 'id' => 'xxe-doctype-entity', 'category' => 'xxe', 'severity' => 'critical',
|
||||||
|
'pattern' => '/<!DOCTYPE[^>]*\[.*<!ENTITY/is' ),
|
||||||
|
array( 'id' => 'xxe-entity-system', 'category' => 'xxe', 'severity' => 'critical',
|
||||||
|
'pattern' => '/<!ENTITY[^>]+SYSTEM\s+["\'](file|https?|ftp|expect|php):\/\//i' ),
|
||||||
|
|
||||||
|
// --- Server-Side Request Forgery (CRS-equivalent) -------------------------------------------------
|
||||||
|
|
||||||
|
array( 'id' => 'ssrf-internal-target', 'category' => 'ssrf', 'severity' => 'high',
|
||||||
|
'pattern' => '/^(https?|gopher|dict|ftp|ldap):\/\/(127\.\d{1,3}\.\d{1,3}\.\d{1,3}|0\.0\.0\.0|localhost|169\.254\.169\.254|\[::1\]|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})([:\/]|$)/i' ),
|
||||||
|
|
||||||
|
// --- Session Fixation (CRS 943-XXX equivalent) -------------------------------------------------
|
||||||
|
|
||||||
|
array( 'id' => 'session-fixation-id-in-url', 'category' => 'session_fixation', 'severity' => 'low',
|
||||||
|
'pattern' => '/[?&](PHPSESSID|JSESSIONID|ASPSESSIONID|ASP\.NET_SessionId)=/i',
|
||||||
|
'sources' => array( 'HEADER:REQUEST_URI' ) ),
|
||||||
|
|
||||||
|
// --- Scanner / Reconnaissance Detection (CRS 913-XXX equivalent) -------------------------------------------------
|
||||||
|
|
||||||
|
array( 'id' => 'scanner-known-tool-ua', 'category' => 'scanner_activity', 'severity' => 'medium',
|
||||||
|
'pattern' => '/\b(sqlmap|nikto|acunetix|nessus|openvas|w3af|havij|nmap scripting engine|masscan|zgrab|dirbuster|gobuster|wfuzz|metasploit|zmeu|wpscan)\b/i',
|
||||||
|
'sources' => array( 'HEADER:HTTP_USER_AGENT' ) ),
|
||||||
|
|
||||||
|
// --- Protocol Anomaly (CRS 920-XXX equivalent) -------------------------------------------------
|
||||||
|
|
||||||
array( 'id' => 'proto-null-byte', 'category' => 'protocol_anomaly', 'severity' => 'medium',
|
array( 'id' => 'proto-null-byte', 'category' => 'protocol_anomaly', 'severity' => 'medium',
|
||||||
'pattern' => '/%00/' ),
|
'pattern' => '/%00/' ),
|
||||||
array( 'id' => 'proto-double-encoding', 'category' => 'protocol_anomaly', 'severity' => 'low',
|
array( 'id' => 'proto-double-encoding', 'category' => 'protocol_anomaly', 'severity' => 'low',
|
||||||
'pattern' => '/%25(2e|2f|5c)/i' ),
|
'pattern' => '/%25(2e|2f|5c)/i' ),
|
||||||
|
array( 'id' => 'proto-crlf-injection', 'category' => 'protocol_anomaly', 'severity' => 'high',
|
||||||
|
'pattern' => '/%0[dD]%0[aA]/' ),
|
||||||
);
|
);
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
@@ -74,6 +150,10 @@ class Argus_WAF_Rules {
|
|||||||
$decoded = rawurldecode( $value );
|
$decoded = rawurldecode( $value );
|
||||||
|
|
||||||
foreach ( self::corpus() as $rule ) {
|
foreach ( self::corpus() as $rule ) {
|
||||||
|
if ( ! empty( $rule['sources'] ) && ! in_array( $source, $rule['sources'], true ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$matched = preg_match( $rule['pattern'], $decoded, $m ) ? $m : ( preg_match( $rule['pattern'], $value, $m ) ? $m : null );
|
$matched = preg_match( $rule['pattern'], $decoded, $m ) ? $m : ( preg_match( $rule['pattern'], $value, $m ) ? $m : null );
|
||||||
if ( null === $matched ) {
|
if ( null === $matched ) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
+11
-8
@@ -1,10 +1,10 @@
|
|||||||
=== ARGUS Defence ===
|
=== ARGUS WordPress Defence ===
|
||||||
Contributors: argus
|
Contributors: argus
|
||||||
Tags: security, firewall, malware, vulnerability, backup
|
Tags: security, firewall, malware, vulnerability, backup
|
||||||
Requires at least: 6.0
|
Requires at least: 6.0
|
||||||
Tested up to: 6.6
|
Tested up to: 7.0
|
||||||
Requires PHP: 7.4
|
Requires PHP: 7.4
|
||||||
Stable tag: 1.0.0
|
Stable tag: 7.23.0
|
||||||
License: GPLv2 or later
|
License: GPLv2 or later
|
||||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
|
||||||
@@ -48,13 +48,16 @@ page-cache feature can make your site faster, not slower.
|
|||||||
|
|
||||||
= How do I update the plugin? =
|
= How do I update the plugin? =
|
||||||
|
|
||||||
Download the latest release ZIP and repeat the installation steps above (WordPress will offer to
|
Like any other WordPress.org plugin: you'll see an update notification right in your WordPress
|
||||||
replace the existing version). Automatic in-dashboard updates depend on an update channel being
|
dashboard whenever a new version is available, and you can update it in one click from there.
|
||||||
configured for your deployment; until then, updates are applied manually the same way the plugin
|
|
||||||
was installed.
|
|
||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
= 1.0.0 =
|
= 7.23.0 =
|
||||||
|
* Firewall: broadened local rule coverage (XXE, SSRF, session fixation, Log4Shell/JNDI, scanner-tool
|
||||||
|
detection, and deeper SQL injection / XSS / PHP injection signatures).
|
||||||
|
* Scanner: fixed a case where a quarantined file's severity label and its content-analysis score
|
||||||
|
could disagree; both are now always shown together and derived consistently.
|
||||||
|
* Console: the running plugin version is now shown in the admin header.
|
||||||
* First production release: firewall, scanner, quarantine, vulnerability protection, backups,
|
* First production release: firewall, scanner, quarantine, vulnerability protection, backups,
|
||||||
cache & performance, and automatic global threat intelligence.
|
cache & performance, and automatic global threat intelligence.
|
||||||
|
|||||||
Reference in New Issue
Block a user