Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8cdbef65a | ||
|
|
806094d6a0 | ||
|
|
0faf78287a | ||
|
|
235169e8ac | ||
|
|
67323f0204 | ||
|
|
f3a00c618b | ||
|
|
d39aa6f9bc | ||
|
|
efb0dfc838 | ||
|
|
7fa580d4d6 | ||
|
|
bd3275d252 | ||
|
|
a0f46c0678 | ||
|
|
82dd287445 | ||
|
|
f48da5e5f1 | ||
|
|
f787d3a070 | ||
|
|
e924c0adf1 | ||
|
|
343642a869 | ||
|
|
2cc4a318ad | ||
|
|
e016046e1f | ||
|
|
0f197d4bfd | ||
|
|
49610d6da0 | ||
|
|
39463e255c | ||
|
|
ecfe29e693 | ||
|
|
488b27ffc4 | ||
|
|
19246c3f5a | ||
|
|
fddf34cd50 | ||
|
|
db1f6d1fd6 | ||
|
|
5f928ba8d1 | ||
|
|
6a7b33b145 | ||
|
|
19ca566549 | ||
|
|
bffd79abdb | ||
|
|
c6faa3e74e | ||
|
|
022d999dc3 | ||
|
|
23e68f7385 | ||
|
|
51dcf25e5a | ||
|
|
50b657b23b | ||
|
|
b7646b720a | ||
|
|
499a74da7b | ||
|
|
5028dfabd5 | ||
|
|
daaff5e873 | ||
|
|
07c8997e1f | ||
|
|
2bf328fa3d | ||
|
|
5462c91106 | ||
|
|
bb36fe77e0 | ||
|
|
1e1dfae997 | ||
|
|
5a1901fd3e | ||
|
|
10e92f4bd3 | ||
|
|
96aa3b20ea | ||
|
|
e6f74fe0c3 | ||
|
|
cd12b06c7a | ||
|
|
d324d5a7a6 | ||
|
|
b5e86863ba | ||
|
|
cfe2cd5369 | ||
|
|
c8e65d8b79 | ||
|
|
1f306340d8 | ||
|
|
db095bc4a0 | ||
|
|
6031ba5163 | ||
|
|
7f7c8de38c | ||
|
|
769f532207 | ||
|
|
cce638adb3 | ||
|
|
446bd5deb9 | ||
|
|
b70f9b0675 | ||
|
|
3b5b86162b | ||
|
|
49b9d8fca5 | ||
|
|
1187ac412c | ||
|
|
c56b4e6e9d | ||
|
|
6676ceb60e | ||
|
|
fdc592d01f | ||
|
|
6e80e24159 | ||
|
|
facfffbc1d | ||
|
|
d33ab11b77 | ||
|
|
1bb03b6c5c | ||
|
|
729c24a83c | ||
|
|
e95bc58ffc | ||
|
|
7cb4c1a8fc | ||
|
|
f20c35fbc5 | ||
|
|
d845e6a57d | ||
|
|
fbfa908695 | ||
|
|
39ef0c2f3f | ||
|
|
9a459b4644 | ||
|
|
342c2ee43f | ||
|
|
105abf4e24 | ||
|
|
d48beb9757 | ||
|
|
4a43572b15 | ||
|
|
20c46d3d26 | ||
|
|
74fdd42561 | ||
|
|
1e4806457e | ||
|
|
da9237171a | ||
|
|
904bf52960 | ||
|
|
8ea7b9f5be | ||
|
|
804a1aae58 | ||
|
|
76e585139e | ||
|
|
f871592d27 | ||
|
|
f1375d3d7d | ||
|
|
72fa8e8901 | ||
|
|
efa84723ee | ||
|
|
39616478c9 | ||
|
|
c1969d4293 | ||
|
|
524b893e65 | ||
|
|
303f31e869 | ||
|
|
20a2a84ee8 | ||
|
|
9127b06836 | ||
|
|
19cb788556 | ||
|
|
e52046437b | ||
|
|
5d36e1058f | ||
|
|
b79ead3dcf | ||
|
|
b0102ea518 | ||
|
|
1b8d7950f5 | ||
|
|
56284b5d20 | ||
|
|
037c2b8cc6 | ||
|
|
b1688a0e16 | ||
|
|
6649b75eb9 | ||
|
|
99bce8b86a | ||
|
|
a214c1656f | ||
|
|
1b1f657b41 | ||
|
|
af032f8f1c | ||
|
|
c4db87af84 | ||
|
|
8a6c9ca4de | ||
|
|
35efd3e485 | ||
|
|
35742e05ab |
@@ -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
|
||||
configuration required.
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Version:** 7.23.2
|
||||
**Requires:** WordPress 6.0+, PHP 7.4+
|
||||
**License:** GPLv2 or later
|
||||
|
||||
## What it does
|
||||
|
||||
- **Firewall** — blocks malicious requests (SQL injection, cross-site scripting, and more)
|
||||
before they reach your site.
|
||||
- **Firewall** — blocks malicious requests (SQL injection, cross-site scripting, remote code
|
||||
execution, XXE, SSRF, and more) before they reach your site.
|
||||
- **Scanner** — regularly checks WordPress core, plugins, and themes for suspicious files
|
||||
and integrity changes, and quarantines confirmed threats instead of just reporting them.
|
||||
- **Vulnerability Protection** — checks your installed plugins, themes, and WordPress core
|
||||
@@ -20,9 +20,40 @@ configuration required.
|
||||
outside the web root.
|
||||
- **Cache & Performance** — an optional page cache that can make your site faster, built
|
||||
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.
|
||||
|
||||
**License**: a 30-day trial with every feature active starts automatically on activation --
|
||||
no registration required to start. Protection never stops when the trial ends; ARGUS Defence →
|
||||
Settings → License shows the days remaining and where to enter a license key to keep Premium
|
||||
features. This section is specific to this self-distributed build.
|
||||
|
||||
## Status
|
||||
|
||||
This is a self-distributed release: it is not listed on WordPress.org and has not gone
|
||||
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,
|
||||
static checks, a manual security pass over authentication/authorization/nonce/SQL
|
||||
handling), but no third-party security audit or penetration test has been performed.
|
||||
- **No CI pipeline yet.** Tests are run manually before each release, not on every commit.
|
||||
- **Tested in sandbox/disposable WordPress environments.** Verification so far — install,
|
||||
activation, all admin pages, deactivation, uninstall — was done on disposable WordPress
|
||||
instances, not on live production sites.
|
||||
- **Automatic updates for ARGUS itself are opt-in, not on by default.** The signed-update
|
||||
mechanism is real and live (this release's own manifest is signed and hosted in this
|
||||
repository) -- add two constants to `wp-config.php` to turn it on. See
|
||||
[Updating](#updating) below for the exact lines and how to verify it's working.
|
||||
Automatic updates for *other* plugins/themes on your site, if you enable that option in
|
||||
Settings, use WordPress's own built-in update system and work without any extra setup.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -31,17 +62,30 @@ Everything works out of the box. There is nothing to configure to get protected.
|
||||
ZIP, then **Install Now**.
|
||||
3. Click **Activate**.
|
||||
|
||||
That's it — ARGUS Defence begins protecting your site automatically. A new **ARGUS
|
||||
Defence** menu appears in your wp-admin sidebar with an overview of your site's
|
||||
protection status.
|
||||
That's it — ARGUS Defence begins protecting your site automatically. A short Welcome screen
|
||||
walks through what's already active and a couple of optional choices (a site-type template,
|
||||
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
|
||||
|
||||
Automatic in-dashboard updates require an update channel to be configured for your
|
||||
deployment (`ARGUS_WPD_UPDATE_MANIFEST_URL` and `ARGUS_WPD_UPDATE_PUBLIC_KEY` in
|
||||
`wp-config.php`). Without one configured, update the plugin the same way you installed
|
||||
it: download the new release ZIP and upload it again through **Plugins → Add New →
|
||||
Upload Plugin** — WordPress will offer to replace the existing version.
|
||||
**Automatic** — add these two lines to `wp-config.php` (above the
|
||||
`/* That's all, stop editing! */` line):
|
||||
|
||||
```php
|
||||
define( 'ARGUS_WPD_UPDATE_MANIFEST_URL', 'https://git-cloud.weboria.eu/Weboria/argus-wp-defence/raw/branch/main/manifest.json' );
|
||||
define( 'ARGUS_WPD_UPDATE_PUBLIC_KEY', 'jBQFZLG+fvtW9y7kmhJa4BTBw8u3lmxbgOcF2ptEhFs=' );
|
||||
```
|
||||
|
||||
From then on ARGUS checks for updates every few hours and installs anything newer
|
||||
automatically, after verifying its Ed25519 signature and SHA-256 checksum. To check
|
||||
immediately instead of waiting: **ARGUS Defence → Settings → Automatic Security Updates →
|
||||
Check for Updates Now**, which also shows the current status (last check, last successful
|
||||
update, last failure).
|
||||
|
||||
**Manual** — download the new release ZIP and upload it through **Plugins → Add New →
|
||||
Upload Plugin** — WordPress will offer to replace the existing version. Works with or
|
||||
without the automatic option configured.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
|
||||
+105
-1
@@ -14,6 +14,28 @@ class Argus_Admin {
|
||||
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__, '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() {
|
||||
@@ -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', __( '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' ) );
|
||||
|
||||
// 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() {
|
||||
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 ) {
|
||||
@@ -121,6 +190,15 @@ class Argus_Admin {
|
||||
$integrity_open = Argus_Findings::count_open( 'integrity' );
|
||||
$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';
|
||||
}
|
||||
|
||||
@@ -530,6 +608,21 @@ class Argus_Admin {
|
||||
}
|
||||
|
||||
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();
|
||||
include ARGUS_WPD_DIR . 'admin/views/anis.php';
|
||||
}
|
||||
@@ -562,6 +655,12 @@ class Argus_Admin {
|
||||
public static function render_settings() {
|
||||
$saved = false;
|
||||
$template_applied = null;
|
||||
$license_result = null;
|
||||
|
||||
if ( class_exists( 'Argus_License' ) && isset( $_POST['argus_wpd_activate_license_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['argus_wpd_activate_license_nonce'] ) ), 'argus_wpd_activate_license' ) ) { // phpcs:ignore
|
||||
Argus_License::set_license_key( sanitize_text_field( wp_unslash( $_POST['license_key'] ?? '' ) ) ); // phpcs:ignore
|
||||
$license_result = Argus_License::validate_now();
|
||||
}
|
||||
|
||||
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
|
||||
@@ -582,6 +681,11 @@ class Argus_Admin {
|
||||
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;
|
||||
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();
|
||||
|
||||
+29
-3
@@ -11,6 +11,7 @@ $protection_display = array(
|
||||
'active' => array( 'label' => __( 'ACTIVE', 'argus-wordpress-defence' ), 'chip' => 'chip-emerald' ),
|
||||
'limited' => array( 'label' => __( 'TEMPORARILY LIMITED', 'argus-wordpress-defence' ), 'chip' => 'chip-amber' ),
|
||||
'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'] ];
|
||||
|
||||
@@ -28,15 +29,33 @@ $next_sync_display = $anis_status['next_sync'] ? wp_date( $date_format, strtotim
|
||||
</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">
|
||||
<p style="font-size:14px;color:var(--text);margin:0 0 20px;">
|
||||
<?php
|
||||
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'] ) {
|
||||
esc_html_e( 'ARGUS Defence is continuing to protect your website while global threat intelligence connectivity is being restored automatically.', 'argus-wordpress-defence' );
|
||||
} 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>
|
||||
@@ -68,7 +87,14 @@ $next_sync_display = $anis_status['next_sync'] ? wp_date( $date_format, strtotim
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include ARGUS_WPD_DIR . 'admin/views/partials/footer.php'; ?>
|
||||
|
||||
@@ -40,75 +40,63 @@ include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
|
||||
|
||||
<div class="argus-wpd-tile-row">
|
||||
<div class="argus-wpd-tile">
|
||||
<div class="argus-wpd-tile-label"><?php esc_html_e( 'WAF Rule Corpus', 'argus-wordpress-defence' ); ?></div>
|
||||
<div class="argus-wpd-tile-value"><?php echo esc_html( number_format_i18n( $waf_rule_count ) ); ?></div>
|
||||
<div class="argus-wpd-tile-label"><?php esc_html_e( 'Firewall', 'argus-wordpress-defence' ); ?></div>
|
||||
<div class="argus-wpd-tile-value" style="font-size:16px;"><span style="color:var(--emerald)"><?php esc_html_e( 'Protecting', 'argus-wordpress-defence' ); ?></span></div>
|
||||
</div>
|
||||
<div class="argus-wpd-tile">
|
||||
<div class="argus-wpd-tile-label"><?php esc_html_e( 'Vulnerability Intelligence', 'argus-wordpress-defence' ); ?></div>
|
||||
<div class="argus-wpd-tile-value" style="font-size:16px;">
|
||||
<?php if ( $vuln_row_count > 0 ) : ?>
|
||||
<span style="color:var(--emerald)"><?php esc_html_e( 'Active', 'argus-wordpress-defence' ); ?></span>
|
||||
<?php else : ?>
|
||||
<span style="color:var(--faint)"><?php esc_html_e( 'Standby', 'argus-wordpress-defence' ); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="argus-wpd-tile-label"><?php esc_html_e( 'Vulnerability Protection', 'argus-wordpress-defence' ); ?></div>
|
||||
<div class="argus-wpd-tile-value" style="font-size:16px;"><span style="color:var(--emerald)"><?php esc_html_e( 'Protecting', 'argus-wordpress-defence' ); ?></span></div>
|
||||
</div>
|
||||
<div class="argus-wpd-tile">
|
||||
<div class="argus-wpd-tile-label"><?php esc_html_e( 'GeoIP Database', 'argus-wordpress-defence' ); ?></div>
|
||||
<div class="argus-wpd-tile-label"><?php esc_html_e( 'Location Awareness', 'argus-wordpress-defence' ); ?></div>
|
||||
<div class="argus-wpd-tile-value" style="font-size:16px;">
|
||||
<?php if ( $geoip_status && ! empty( $geoip_status['range_count'] ) ) : ?>
|
||||
<span style="color:var(--emerald)"><?php esc_html_e( 'Loaded', 'argus-wordpress-defence' ); ?></span>
|
||||
<span style="color:var(--emerald)"><?php esc_html_e( 'Active', 'argus-wordpress-defence' ); ?></span>
|
||||
<?php else : ?>
|
||||
<span style="color:var(--faint)"><?php esc_html_e( 'Building', 'argus-wordpress-defence' ); ?></span>
|
||||
<span style="color:var(--faint)"><?php esc_html_e( 'Activating', 'argus-wordpress-defence' ); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="argus-wpd-panel">
|
||||
<h2><?php esc_html_e( 'Local Intelligence Sources', 'argus-wordpress-defence' ); ?></h2>
|
||||
<h2><?php esc_html_e( 'What\'s Protecting Your Site', 'argus-wordpress-defence' ); ?></h2>
|
||||
<div class="argus-wpd-ioc-row">
|
||||
<span class="argus-wpd-ioc-type">WAF</span>
|
||||
<span class="argus-wpd-ioc-desc"><?php echo esc_html( sprintf( __( 'Application-layer signature corpus (SQLi/XSS/RCE/path traversal/protocol anomaly) -- %d rules, updated with this plugin.', 'argus-wordpress-defence' ), $waf_rule_count ) ); ?></span>
|
||||
<span class="argus-wpd-ioc-type"><?php esc_html_e( 'Firewall', 'argus-wordpress-defence' ); ?></span>
|
||||
<span class="argus-wpd-ioc-desc"><?php esc_html_e( 'Blocks malicious requests -- SQL injection, cross-site scripting, remote code execution, path traversal, and more -- before they reach your site. Kept up to date automatically.', 'argus-wordpress-defence' ); ?></span>
|
||||
</div>
|
||||
<div class="argus-wpd-ioc-row">
|
||||
<span class="argus-wpd-ioc-type">VULN</span>
|
||||
<span class="argus-wpd-ioc-type"><?php esc_html_e( 'Vulnerability Protection', 'argus-wordpress-defence' ); ?></span>
|
||||
<span class="argus-wpd-ioc-desc">
|
||||
<?php
|
||||
echo esc_html(
|
||||
$vuln_row_count > 0
|
||||
? __( 'Vulnerability intelligence -- actively matching installed plugins and themes against known security issues.', 'argus-wordpress-defence' )
|
||||
: __( 'Vulnerability intelligence -- WordPress core is checked live against WordPress.org. Plugin and theme results activate automatically as intelligence becomes available.', 'argus-wordpress-defence' )
|
||||
? __( 'Actively protecting your installed plugins and themes by checking them against known security issues.', 'argus-wordpress-defence' )
|
||||
: __( 'Actively protecting WordPress core against known security issues. Plugin and theme protection activates automatically as new information becomes available.', 'argus-wordpress-defence' )
|
||||
);
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="argus-wpd-ioc-row">
|
||||
<span class="argus-wpd-ioc-type">GEOIP</span>
|
||||
<span class="argus-wpd-ioc-type"><?php esc_html_e( 'Location Awareness', 'argus-wordpress-defence' ); ?></span>
|
||||
<span class="argus-wpd-ioc-desc">
|
||||
<?php
|
||||
if ( $geoip_status && ! empty( $geoip_status['range_count'] ) ) {
|
||||
echo esc_html(
|
||||
sprintf(
|
||||
__( 'Self-hosted RIR delegation database -- %s IPv4 ranges, last refreshed %s.', 'argus-wordpress-defence' ),
|
||||
number_format_i18n( $geoip_status['range_count'] ),
|
||||
$geoip_status['last_refresh'] ? human_time_diff( strtotime( $geoip_status['last_refresh'] . ' UTC' ) ) . ' ' . __( 'ago', 'argus-wordpress-defence' ) : __( 'never', 'argus-wordpress-defence' )
|
||||
)
|
||||
);
|
||||
esc_html_e( 'Identifies where threats are coming from automatically, with no external service or configuration required.', 'argus-wordpress-defence' );
|
||||
} else {
|
||||
esc_html_e( 'Self-hosted RIR delegation database has not built its first snapshot yet -- refreshes automatically once daily, no action needed.', 'argus-wordpress-defence' );
|
||||
esc_html_e( 'Activating automatically -- no action needed.', 'argus-wordpress-defence' );
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="argus-wpd-ioc-row">
|
||||
<span class="argus-wpd-ioc-type">ANIS</span>
|
||||
<span class="argus-wpd-ioc-type"><?php esc_html_e( 'Global Threat Network', 'argus-wordpress-defence' ); ?></span>
|
||||
<span class="argus-wpd-ioc-desc">
|
||||
<?php
|
||||
if ( 'not_connected' === $anis_status['protection'] ) {
|
||||
esc_html_e( 'Global threat intelligence -- connecting automatically.', 'argus-wordpress-defence' );
|
||||
esc_html_e( 'Connecting automatically -- no action needed.', 'argus-wordpress-defence' );
|
||||
} else {
|
||||
esc_html_e( 'Global threat intelligence -- automatically updated to help identify malicious sources.', 'argus-wordpress-defence' );
|
||||
esc_html_e( 'Helps identify and block malicious sources seen across other protected sites, automatically.', 'argus-wordpress-defence' );
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
|
||||
@@ -36,6 +36,34 @@ $score_offset = $score_circ * ( 1 - $score / 100 );
|
||||
</div>
|
||||
<?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-card argus-wpd-score">
|
||||
<svg viewBox="0 0 128 128">
|
||||
@@ -71,6 +99,19 @@ $score_offset = $score_circ * ( 1 - $score / 100 );
|
||||
</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-head">
|
||||
<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' ) ),
|
||||
'limited' => array( 'chip-amber', __( 'LIMITED', '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'];
|
||||
?>
|
||||
|
||||
@@ -48,6 +48,7 @@ $argus_nav_items = array(
|
||||
<?php echo esc_html( $argus_protection_display['label'] ); ?>
|
||||
· <?php echo esc_html( $argus_mode_labels[ $argus_mode ] ?? $argus_mode ); ?>
|
||||
</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>
|
||||
</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>
|
||||
<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 ) ) : ?>
|
||||
<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>
|
||||
<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>
|
||||
<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(); ?>
|
||||
<table class="widefat" style="border:none;background:transparent;margin-top:14px;">
|
||||
<tbody>
|
||||
@@ -171,8 +177,49 @@ include ARGUS_WPD_DIR . 'admin/views/partials/header.php';
|
||||
<?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>
|
||||
</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>
|
||||
|
||||
<?php if ( class_exists( 'Argus_License' ) ) : ?>
|
||||
<div class="argus-wpd-panel">
|
||||
<h2><?php esc_html_e( 'License', 'argus-wordpress-defence' ); ?></h2>
|
||||
<?php $license_summary = Argus_License::summary(); ?>
|
||||
<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
|
||||
if ( 'licensed' === $license_summary['status'] ) {
|
||||
esc_html_e( 'LICENSED', 'argus-wordpress-defence' );
|
||||
} elseif ( 'trial' === $license_summary['status'] ) {
|
||||
echo esc_html( sprintf(
|
||||
/* translators: %d: number of days remaining in the free trial */
|
||||
_n( 'TRIAL -- %d DAY LEFT', 'TRIAL -- %d DAYS LEFT', $license_summary['days_remaining'], 'argus-wordpress-defence' ),
|
||||
$license_summary['days_remaining']
|
||||
) );
|
||||
} else {
|
||||
esc_html_e( 'TRIAL ENDED', 'argus-wordpress-defence' );
|
||||
}
|
||||
?>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<?php if ( ! empty( $license_result ) ) : ?>
|
||||
<p style="font-size:12.5px;margin:8px 0 0;color:<?php echo ! empty( $license_result['success'] ) ? 'var(--emerald)' : 'var(--rose)'; ?>;"><?php echo esc_html( $license_result['message'] ); ?></p>
|
||||
<?php endif; ?>
|
||||
<form method="post" style="margin-top:14px;display:flex;gap:8px;align-items:center;">
|
||||
<?php wp_nonce_field( 'argus_wpd_activate_license', 'argus_wpd_activate_license_nonce' ); ?>
|
||||
<input type="text" name="license_key" placeholder="<?php esc_attr_e( 'ARGUS-XXXX-XXXX-...', 'argus-wordpress-defence' ); ?>" value="<?php echo esc_attr( $license_summary['has_key'] ? Argus_License::license_key() : '' ); ?>" style="flex:1;max-width:380px;padding:8px 10px;border-radius:6px;border:1px solid var(--border-strong);background:var(--panel-2);color:var(--text);font-family:var(--mono);font-size:13px;" />
|
||||
<button type="submit" class="button button-primary"><?php esc_html_e( 'Activate', 'argus-wordpress-defence' ); ?></button>
|
||||
</form>
|
||||
<?php if ( $license_summary['purchase_url'] ) : ?>
|
||||
<p style="font-size:12.5px;margin:12px 0 0;"><a href="<?php echo esc_url( $license_summary['purchase_url'] ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Get a license', 'argus-wordpress-defence' ); ?></a></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="argus-wpd-panel">
|
||||
<h2><?php esc_html_e( 'Website Acceleration', 'argus-wordpress-defence' ); ?></h2>
|
||||
<p style="color:var(--faint);font-size:12.5px;margin:-8px 0 12px;"><?php esc_html_e( 'Cache & Performance now has its own dedicated page -- speed up your website, reduce load, and see what\'s being accelerated.', 'argus-wordpress-defence' ); ?></p>
|
||||
|
||||
@@ -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 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.
|
||||
* Version: 1.0.0
|
||||
* Version: 7.23.2
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 7.4
|
||||
* Author: ARGUS
|
||||
@@ -28,7 +28,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define( 'ARGUS_WPD_VERSION', '1.0.0' );
|
||||
define( 'ARGUS_WPD_VERSION', '7.23.2' );
|
||||
define( 'ARGUS_WPD_FILE', __FILE__ );
|
||||
define( 'ARGUS_WPD_DIR', plugin_dir_path( __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 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) ---------- */
|
||||
.argus-wpd-gear {
|
||||
margin-left: 10px; width: 38px; height: 38px; border-radius: 9px; flex-shrink: 0;
|
||||
@@ -295,6 +315,7 @@
|
||||
|
||||
/* ---------- 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.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: 640px) { .argus-wpd-metric-grid { grid-template-columns: 1fr; } }
|
||||
.argus-wpd-metric-card {
|
||||
|
||||
+27
-1
@@ -8,12 +8,20 @@
|
||||
# the result against an explicit allowlist before packaging, failing
|
||||
# closed on anything unexpected rather than silently shipping it.
|
||||
#
|
||||
# Usage: bin/build-release.sh [git-ref] (defaults to HEAD)
|
||||
# Usage: bin/build-release.sh [git-ref] [channel]
|
||||
# channel: "self" (default) -- the self-distributed release (Gitea
|
||||
# releases page), includes the signed self-update client, since
|
||||
# nothing prohibits it outside WordPress.org.
|
||||
# "wporg" -- strips the self-update client too (and the
|
||||
# license/trial system, stripped either way) -- required before
|
||||
# any submission to the WordPress.org Plugin Directory, which
|
||||
# prohibits a plugin using any update channel but its own.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REF="${1:-HEAD}"
|
||||
CHANNEL="${2:-self}"
|
||||
BUILD_DIR="$(mktemp -d)"
|
||||
STAGE_DIR="${BUILD_DIR}/argus-wordpress-defence"
|
||||
OUT_DIR="${REPO_ROOT}/dist"
|
||||
@@ -31,9 +39,27 @@ rm -rf \
|
||||
"${STAGE_DIR}/tests" \
|
||||
"${STAGE_DIR}/README.md" \
|
||||
"${STAGE_DIR}/ARGUS_WORDPRESS_SECURITY_ARCHITECTURE.md" \
|
||||
"${STAGE_DIR}/WORDPRESS_ORG_READINESS_AUDIT.md" \
|
||||
"${STAGE_DIR}/.gitignore" \
|
||||
"${STAGE_DIR}/bin"
|
||||
|
||||
if [ "${CHANNEL}" = "wporg" ]; then
|
||||
# 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 -- and must stay genuinely,
|
||||
# permanently functional with no license/trial requirement (WordPress.org
|
||||
# explicitly prohibits trialware). class_exists() guards every reference
|
||||
# to both classes elsewhere in the codebase, so removing the files here is
|
||||
# sufficient; nothing else needs to change per build.
|
||||
echo "==> [wporg channel] Removing the self-update client (WordPress.org must be the only update channel)"
|
||||
rm -f "${STAGE_DIR}/includes/class-argus-update-client.php"
|
||||
echo "==> [wporg channel] Removing the license/trial system (WordPress.org prohibits trialware)"
|
||||
rm -f "${STAGE_DIR}/includes/class-argus-license.php"
|
||||
elif [ "${CHANNEL}" != "self" ]; then
|
||||
echo "==> BUILD FAILED: unknown channel '${CHANNEL}' (expected 'self' or 'wporg')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Allowlist audit -- fail closed on anything unexpected"
|
||||
UNEXPECTED=0
|
||||
while IFS= read -r -d '' item; do
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Signs an update manifest for Argus_Update_Client (includes/class-argus-update-client.php).
|
||||
* Never shipped in any release ZIP -- a dev/release-time tool only.
|
||||
*
|
||||
* Usage:
|
||||
* php bin/sign-manifest.php \
|
||||
* --private-key=<base64 Ed25519 secret key> \
|
||||
* --version=7.23.1 \
|
||||
* --package-url=https://.../argus-wordpress-defence-7.23.1.zip \
|
||||
* --sha256=<hex sha256 of that exact zip> \
|
||||
* [--min-php=7.4] [--min-wp=6.0] [--critical] \
|
||||
* [--out=manifest.json]
|
||||
*
|
||||
* The signed payload's field set and order (version, released_at,
|
||||
* package_url, sha256, min_php, min_wp, critical) MUST exactly match
|
||||
* Argus_Update_Client::canonical_payload() -- any drift and every
|
||||
* signature this produces fails verification client-side.
|
||||
*/
|
||||
|
||||
$args = array();
|
||||
foreach ( $argv as $arg ) {
|
||||
if ( 0 === strpos( $arg, '--' ) ) {
|
||||
$parts = explode( '=', substr( $arg, 2 ), 2 );
|
||||
$args[ $parts[0] ] = $parts[1] ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
function required( $args, $key ) {
|
||||
if ( empty( $args[ $key ] ) ) {
|
||||
fwrite( STDERR, "Missing required --{$key}\n" );
|
||||
exit( 1 );
|
||||
}
|
||||
return $args[ $key ];
|
||||
}
|
||||
|
||||
$private_key_b64 = required( $args, 'private-key' );
|
||||
$version = required( $args, 'version' );
|
||||
$package_url = required( $args, 'package-url' );
|
||||
$sha256 = required( $args, 'sha256' );
|
||||
$min_php = $args['min-php'] ?? '7.4';
|
||||
$min_wp = $args['min-wp'] ?? '6.0';
|
||||
$critical = ! empty( $args['critical'] );
|
||||
$released_at = gmdate( 'c' );
|
||||
$out = $args['out'] ?? ( dirname( __DIR__ ) . '/manifest.json' );
|
||||
|
||||
if ( 64 !== strlen( $sha256 ) || ! ctype_xdigit( $sha256 ) ) {
|
||||
fwrite( STDERR, "--sha256 must be a 64-character hex string (run: sha256sum <zip>)\n" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
$private_key = base64_decode( $private_key_b64, true );
|
||||
if ( false === $private_key || SODIUM_CRYPTO_SIGN_SECRETKEYBYTES !== strlen( $private_key ) ) {
|
||||
fwrite( STDERR, "--private-key is not a valid base64-encoded Ed25519 secret key\n" );
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
// Must match Argus_Update_Client::canonical_payload() exactly -- same key
|
||||
// set, same order, same JSON_UNESCAPED_SLASHES flag.
|
||||
$ordered = array(
|
||||
'version' => $version,
|
||||
'released_at' => $released_at,
|
||||
'package_url' => $package_url,
|
||||
'sha256' => $sha256,
|
||||
'min_php' => $min_php,
|
||||
'min_wp' => $min_wp,
|
||||
'critical' => $critical,
|
||||
);
|
||||
$canonical = json_encode( $ordered, JSON_UNESCAPED_SLASHES );
|
||||
|
||||
$signature = sodium_crypto_sign_detached( $canonical, $private_key );
|
||||
$manifest = $ordered;
|
||||
$manifest['signature'] = base64_encode( $signature );
|
||||
|
||||
file_put_contents( $out, json_encode( $manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) . "\n" );
|
||||
|
||||
echo "Wrote {$out}\n";
|
||||
echo " version: {$version}\n";
|
||||
echo " package_url: {$package_url}\n";
|
||||
echo " sha256: {$sha256}\n";
|
||||
echo " critical: " . ( $critical ? 'true' : 'false' ) . "\n";
|
||||
@@ -36,6 +36,20 @@ class Argus_Activator {
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
@@ -558,7 +558,7 @@ class Argus_ANIS_Client {
|
||||
|
||||
return array(
|
||||
'configured' => self::is_configured(),
|
||||
'enabled' => (bool) Argus_Settings::get( 'anis_enabled', true ),
|
||||
'enabled' => (bool) Argus_Settings::get( 'anis_enabled', false ),
|
||||
'connected' => self::is_connected(),
|
||||
'status' => get_option( self::STATUS_OPTION, 'unconfigured' ),
|
||||
'last_error' => get_option( self::LAST_ERROR_OPTION, '' ),
|
||||
@@ -574,6 +574,9 @@ class Argus_ANIS_Client {
|
||||
}
|
||||
|
||||
protected static function protection_state() {
|
||||
if ( ! (bool) Argus_Settings::get( 'anis_enabled', false ) ) {
|
||||
return 'disabled';
|
||||
}
|
||||
if ( ! self::is_enabled() ) {
|
||||
return 'not_connected';
|
||||
}
|
||||
|
||||
@@ -7,20 +7,38 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
class Argus_Auto_Update {
|
||||
|
||||
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( 'site_option_auto_update_plugins', array( __CLASS__, 'ensure_in_list' ) );
|
||||
add_filter( '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_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 ) {
|
||||
return true;
|
||||
}
|
||||
if ( Argus_Settings::get( 'auto_update_all_enabled', true ) ) {
|
||||
return true;
|
||||
}
|
||||
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();
|
||||
if ( ! in_array( ARGUS_WPD_BASENAME, $list, true ) ) {
|
||||
$list[] = ARGUS_WPD_BASENAME;
|
||||
@@ -37,9 +55,15 @@ class Argus_Auto_Update {
|
||||
}
|
||||
|
||||
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(
|
||||
'label' => __( 'Enabled and Protected', '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' ),
|
||||
'label' => __( 'Enabled for ARGUS Only', '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' ),
|
||||
'file_access' => __( 'Path Traversal / LFI', '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 ) );
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Premium-build-only, same as Argus_Update_Client: excluded from the
|
||||
// WordPress.org release ZIP (bin/build-release.sh), guarded everywhere with
|
||||
// class_exists() so the free build never references it. WP.org's guidelines
|
||||
// prohibit trialware in a plugin submitted to the directory -- the free
|
||||
// build stays genuinely, permanently functional, with no trial timer and no
|
||||
// license requirement anywhere in it. This class exists only in a
|
||||
// separately-distributed Premium build.
|
||||
class Argus_License {
|
||||
|
||||
const TRIAL_DAYS = 30;
|
||||
|
||||
const TRIAL_STARTED_OPTION = 'argus_wpd_license_trial_started_at';
|
||||
const LICENSE_KEY_OPTION = 'argus_wpd_license_key';
|
||||
const LICENSE_STATUS_OPTION = 'argus_wpd_license_validation_status';
|
||||
const LICENSE_CHECKED_OPTION = 'argus_wpd_license_last_checked';
|
||||
|
||||
public static function ensure_trial_started() {
|
||||
if ( ! get_option( self::TRIAL_STARTED_OPTION ) ) {
|
||||
update_option( self::TRIAL_STARTED_OPTION, time(), false );
|
||||
}
|
||||
}
|
||||
|
||||
public static function trial_started_at() {
|
||||
return (int) get_option( self::TRIAL_STARTED_OPTION, time() );
|
||||
}
|
||||
|
||||
public static function trial_ends_at() {
|
||||
return self::trial_started_at() + ( self::TRIAL_DAYS * DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
public static function days_remaining() {
|
||||
$remaining_secs = self::trial_ends_at() - time();
|
||||
return max( 0, (int) ceil( $remaining_secs / DAY_IN_SECONDS ) );
|
||||
}
|
||||
|
||||
public static function license_key() {
|
||||
if ( defined( 'ARGUS_WPD_LICENSE_KEY' ) && ARGUS_WPD_LICENSE_KEY ) {
|
||||
return trim( ARGUS_WPD_LICENSE_KEY );
|
||||
}
|
||||
return trim( (string) get_option( self::LICENSE_KEY_OPTION, '' ) );
|
||||
}
|
||||
|
||||
// Saving a submitted key never marks it valid by itself -- the caller
|
||||
// still has to call validate_now() to actually check it against the
|
||||
// configured validation server, same as entering any license key
|
||||
// anywhere always requires a real check before it does anything.
|
||||
public static function set_license_key( $key ) {
|
||||
update_option( self::LICENSE_KEY_OPTION, trim( (string) $key ), false );
|
||||
delete_option( self::LICENSE_STATUS_OPTION );
|
||||
}
|
||||
|
||||
public static function validation_endpoint() {
|
||||
return defined( 'ARGUS_WPD_LICENSE_VALIDATE_URL' ) ? ARGUS_WPD_LICENSE_VALIDATE_URL : '';
|
||||
}
|
||||
|
||||
// Honest, not fabricated: a license key alone never counts as "licensed"
|
||||
// unless there's a real validation endpoint configured to actually check
|
||||
// it against. No key -> unlicensed. Key present but no validation
|
||||
// endpoint configured -> still unlicensed, not a silent green light.
|
||||
public static function is_licensed() {
|
||||
if ( '' === self::license_key() || '' === self::validation_endpoint() ) {
|
||||
return false;
|
||||
}
|
||||
return 'valid' === get_option( self::LICENSE_STATUS_OPTION, '' );
|
||||
}
|
||||
|
||||
public static function validate_now() {
|
||||
$key = self::license_key();
|
||||
$url = self::validation_endpoint();
|
||||
if ( '' === $key ) {
|
||||
return array( 'success' => false, 'message' => __( 'Enter a license key first.', 'argus-wordpress-defence' ) );
|
||||
}
|
||||
if ( '' === $url ) {
|
||||
return array( 'success' => false, 'message' => __( 'No license validation server is configured for this deployment.', 'argus-wordpress-defence' ) );
|
||||
}
|
||||
|
||||
$response = wp_remote_post(
|
||||
$url,
|
||||
array(
|
||||
'timeout' => 15,
|
||||
'headers' => array( 'Content-Type' => 'application/json' ),
|
||||
'body' => wp_json_encode( array( 'license_key' => $key, 'domain' => wp_parse_url( home_url(), PHP_URL_HOST ) ) ),
|
||||
)
|
||||
);
|
||||
|
||||
update_option( self::LICENSE_CHECKED_OPTION, current_time( 'mysql', true ), false );
|
||||
|
||||
if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
|
||||
update_option( self::LICENSE_STATUS_OPTION, 'error', false );
|
||||
return array( 'success' => false, 'message' => __( 'Could not reach the license server. Try again shortly.', 'argus-wordpress-defence' ) );
|
||||
}
|
||||
|
||||
$data = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$valid = is_array( $data ) && ! empty( $data['valid'] );
|
||||
update_option( self::LICENSE_STATUS_OPTION, $valid ? 'valid' : 'invalid', false );
|
||||
|
||||
return array(
|
||||
'success' => $valid,
|
||||
'message' => $valid
|
||||
? __( 'License activated.', 'argus-wordpress-defence' )
|
||||
: __( 'That license key is not valid or has expired.', 'argus-wordpress-defence' ),
|
||||
);
|
||||
}
|
||||
|
||||
public static function status() {
|
||||
if ( self::is_licensed() ) {
|
||||
return 'licensed';
|
||||
}
|
||||
return self::days_remaining() > 0 ? 'trial' : 'trial_expired';
|
||||
}
|
||||
|
||||
public static function summary() {
|
||||
return array(
|
||||
'status' => self::status(),
|
||||
'days_remaining' => self::days_remaining(),
|
||||
'trial_ends_at' => gmdate( 'Y-m-d H:i:s', self::trial_ends_at() ),
|
||||
'has_key' => '' !== self::license_key(),
|
||||
'purchase_url' => defined( 'ARGUS_WPD_LICENSE_PURCHASE_URL' ) ? ARGUS_WPD_LICENSE_PURCHASE_URL : '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -141,36 +141,68 @@ class Argus_Malware_Scanner {
|
||||
$rel = str_replace( wp_normalize_path( ABSPATH ), '', wp_normalize_path( $path ) );
|
||||
|
||||
if ( self::is_restored_trusted( $rel, hash_file( 'sha256', $path ) ) ) {
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
$size = filesize( $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(
|
||||
$path,
|
||||
$rel,
|
||||
array(
|
||||
'engine' => 'Argus_Malware_Scanner',
|
||||
'rule' => 'php-in-uploads',
|
||||
'type' => 'signature',
|
||||
'severity' => 'critical',
|
||||
'engine' => 'Argus_Malware_Scanner',
|
||||
'rule' => 'php-in-uploads',
|
||||
'type' => 'signature',
|
||||
'severity' => $final_severity,
|
||||
'content_score' => $content_score,
|
||||
'matched_rules' => wp_list_pluck( $matched, 'id' ),
|
||||
)
|
||||
);
|
||||
$neutralized = false !== $result;
|
||||
|
||||
$finding_id = Argus_Findings::record(
|
||||
'malware',
|
||||
'critical',
|
||||
$final_severity,
|
||||
array(
|
||||
'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.',
|
||||
'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' ),
|
||||
'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_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.',
|
||||
),
|
||||
array(
|
||||
@@ -178,6 +210,7 @@ class Argus_Malware_Scanner {
|
||||
'file_path' => $rel,
|
||||
'file_mtime' => $mtime,
|
||||
'neutralized' => $neutralized,
|
||||
'content_score' => $content_score,
|
||||
),
|
||||
Argus_Findings::STATUS_OPEN
|
||||
);
|
||||
@@ -196,7 +229,8 @@ class Argus_Malware_Scanner {
|
||||
}
|
||||
|
||||
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 ) {
|
||||
if ( ! is_dir( $dir ) ) {
|
||||
@@ -210,6 +244,21 @@ class Argus_Malware_Scanner {
|
||||
if ( ! $file->isFile() || 'php' !== strtolower( $file->getExtension() ) ) {
|
||||
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 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@ class Argus_Plugin {
|
||||
Argus_DB::maybe_upgrade();
|
||||
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
|
||||
|
||||
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_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' ) ) {
|
||||
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 );
|
||||
|
||||
// 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(
|
||||
Argus_DB::table( 'quarantine' ),
|
||||
array(
|
||||
@@ -102,10 +111,13 @@ class Argus_Quarantine {
|
||||
'detection_rule' => $detection['rule'] ?? null,
|
||||
'detection_type' => $detection['type'],
|
||||
'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,
|
||||
'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;
|
||||
|
||||
@@ -24,6 +24,7 @@ class Argus_Settings {
|
||||
'upload_scan_enabled' => true,
|
||||
'vuln_intel_enabled' => true,
|
||||
'geoip_rir_enabled' => true,
|
||||
'auto_update_all_enabled' => true,
|
||||
'static_cache_enabled' => false,
|
||||
'static_cache_ttl_secs' => 3600,
|
||||
'cache_stale_grace_secs' => 600,
|
||||
@@ -45,7 +46,15 @@ class Argus_Settings {
|
||||
'exceptions' => array(),
|
||||
'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_license_key' => '',
|
||||
|
||||
|
||||
@@ -118,12 +118,15 @@ class Argus_Update_Client {
|
||||
|
||||
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 );
|
||||
// Any newer, compatible, signature-verified version is installed --
|
||||
// 'critical' is informational only (used in the success message and
|
||||
// by Argus_Events severity), not an install gate. It used to be: a
|
||||
// non-critical manifest was recorded as "update_available" and left
|
||||
// there permanently, since nothing else ever called install_update()
|
||||
// for it -- no button, no cron path, nothing. That made "automatic
|
||||
// updates" only ever true for updates someone remembered to flag
|
||||
// critical, which defeats the actual feature.
|
||||
return self::install_update( $manifest );
|
||||
}
|
||||
|
||||
public static function verify_signature( array $manifest ) {
|
||||
|
||||
@@ -11,11 +11,12 @@ class Argus_WAF_Rules {
|
||||
}
|
||||
|
||||
$rules = array(
|
||||
|
||||
|
||||
// --- SQL Injection (CRS 942-XXX equivalent) -------------------------------------------------
|
||||
|
||||
array( 'id' => 'sqli-union-select', 'category' => 'sql_injection', 'severity' => 'critical',
|
||||
'pattern' => '/\bunion\b[^\w]{1,20}\bselect\b/i' ),
|
||||
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' ),
|
||||
array( 'id' => 'sqli-information-schema', 'category' => 'sql_injection', 'severity' => 'critical',
|
||||
'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' ),
|
||||
array( 'id' => 'sqli-stacked-comment', 'category' => 'sql_injection', 'severity' => 'high',
|
||||
'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',
|
||||
'pattern' => '/<\s*script[\s>\/]/i' ),
|
||||
|
||||
array( 'id' => 'xss-event-handler', 'category' => 'xss', 'severity' => 'high',
|
||||
'pattern' => '/\bon(error|load|mouseover|click|focus)\s*=\s*["\']?[^"\'>]*[\(\{]/i' ),
|
||||
array( 'id' => 'xss-javascript-uri', 'category' => 'xss', 'severity' => 'high',
|
||||
'pattern' => '/javascript\s*:\s*[^\s]/i' ),
|
||||
array( 'id' => 'xss-svg-onload', 'category' => 'xss', 'severity' => 'high',
|
||||
'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',
|
||||
'pattern' => '/;\s*(cat|ls|whoami|id|uname|wget|curl)\s/i' ),
|
||||
array( 'id' => 'cmdi-backtick-subshell', 'category' => 'rce', 'severity' => 'critical',
|
||||
'pattern' => '/`[^`]{1,80}`|\$\([^\)]{1,80}\)/' ),
|
||||
|
||||
// --- PHP Injection (CRS 933-XXX equivalent) -------------------------------------------------
|
||||
|
||||
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',
|
||||
'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',
|
||||
'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',
|
||||
'pattern' => '/(\.\.\/|\.\.\\\\|%2e%2e%2f|%252e%252e%252f)/i' ),
|
||||
array( 'id' => 'lfi-sensitive-file', 'category' => 'file_access', 'severity' => 'high',
|
||||
'pattern' => '/\/etc\/(passwd|shadow|hosts)\b|wp-config\.php/i' ),
|
||||
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',
|
||||
'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',
|
||||
'pattern' => '/%00/' ),
|
||||
array( 'id' => 'proto-double-encoding', 'category' => 'protocol_anomaly', 'severity' => 'low',
|
||||
'pattern' => '/%25(2e|2f|5c)/i' ),
|
||||
array( 'id' => 'proto-crlf-injection', 'category' => 'protocol_anomaly', 'severity' => 'high',
|
||||
'pattern' => '/%0[dD]%0[aA]/' ),
|
||||
);
|
||||
|
||||
return $rules;
|
||||
@@ -74,6 +150,10 @@ class Argus_WAF_Rules {
|
||||
$decoded = rawurldecode( $value );
|
||||
|
||||
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 );
|
||||
if ( null === $matched ) {
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "7.52.0",
|
||||
"released_at": "2026-09-23T09:51:34+00:00",
|
||||
"package_url": "https://git-cloud.weboria.eu/Weboria/argus-wp-defence/releases/download/v7.52.0/argus-wordpress-defence-7.52.0.zip",
|
||||
"sha256": "c954611e46daefc04d34313a5b3d321b5f0eac5a8a80c6b791aa72f6fadea82a",
|
||||
"min_php": "7.4",
|
||||
"min_wp": "6.0",
|
||||
"critical": false,
|
||||
"summary": "* Manual IP blocking now lets you choose the block duration -- Temporary, Extended, or Permanent\n -- instead of always applying a fixed 24-hour block.\n* Fixed remote management actions sent from the WAD portal sometimes being rejected when they\n contained certain special characters (such as accented letters or punctuation) in a reason or\n note field.\n* Fixed a scan that could get permanently stuck reporting \"already in progress\" if it was\n interrupted; a new scan can now always be started.\n* Fixed deleting a backup that was already removed being reported as an error instead of simply\n confirming it's gone.",
|
||||
"signature": "4jOlj5HY/GIuDiy0iqmqADegPHXJWC22KZoHlKyVeRVfuCEFDPtscPEtpaYkbAZ8vC+mkCQjrGdfVO+vIxBjBA=="
|
||||
}
|
||||
+22
-8
@@ -1,10 +1,10 @@
|
||||
=== ARGUS Defence ===
|
||||
=== ARGUS WordPress Defence ===
|
||||
Contributors: argus
|
||||
Tags: security, firewall, malware, vulnerability, backup
|
||||
Requires at least: 6.0
|
||||
Tested up to: 6.6
|
||||
Tested up to: 7.0
|
||||
Requires PHP: 7.4
|
||||
Stable tag: 1.0.0
|
||||
Stable tag: 7.23.2
|
||||
License: GPLv2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
@@ -48,13 +48,27 @@ page-cache feature can make your site faster, not slower.
|
||||
|
||||
= How do I update the plugin? =
|
||||
|
||||
Download the latest release ZIP and repeat the installation steps above (WordPress will offer to
|
||||
replace the existing version). Automatic in-dashboard updates depend on an update channel being
|
||||
configured for your deployment; until then, updates are applied manually the same way the plugin
|
||||
was installed.
|
||||
Like any other WordPress.org plugin: you'll see an update notification right in your WordPress
|
||||
dashboard whenever a new version is available, and you can update it in one click from there.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.0 =
|
||||
= 7.23.2 =
|
||||
* Settings: added a License section -- a 30-day trial with everything active, then enter a
|
||||
license key to keep Premium features. Protection never stops regardless of license status.
|
||||
* Intelligence page: rewrote descriptions in plain language focused on what's protecting your
|
||||
site, not internal implementation details.
|
||||
|
||||
= 7.23.1 =
|
||||
* Automatic updates: fixed a bug where a normal (non-critical) update would be detected and
|
||||
shown as available, but never actually installed -- now any newer, compatible, verified
|
||||
update installs automatically, the same way updates to other plugins already do.
|
||||
|
||||
= 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,
|
||||
cache & performance, and automatic global threat intelligence.
|
||||
|
||||
Reference in New Issue
Block a user