Compare commits

...
119 Commits
Author SHA1 Message Date
tech a8cdbef65a Update signed manifest to 7.52.0 2026-09-23 09:51:35 +00:00
tech 806094d6a0 Update signed manifest to 7.51.0 2026-09-19 16:44:44 +00:00
tech 0faf78287a Update signed manifest to 7.49.0 2026-09-19 10:22:45 +00:00
tech 235169e8ac Update signed manifest to 7.46.0 2026-09-17 23:20:51 +00:00
tech 67323f0204 Update signed manifest to 7.45.5 2026-09-17 22:40:55 +00:00
tech f3a00c618b Update signed manifest to 7.45.4 2026-09-17 22:28:23 +00:00
tech d39aa6f9bc Update signed manifest to 7.45.1 2026-09-17 18:11:59 +00:00
tech efb0dfc838 Update signed manifest to 7.45.0 2026-09-17 17:48:05 +00:00
tech 7fa580d4d6 Update signed manifest to 7.44.0 2026-09-17 16:36:26 +00:00
tech bd3275d252 Update signed manifest to 7.43.0 2026-09-17 12:33:03 +00:00
tech a0f46c0678 Update signed manifest to 7.42.0 2026-09-17 12:12:42 +00:00
tech 82dd287445 Update signed manifest to 7.41.0 2026-09-17 11:36:04 +00:00
tech f48da5e5f1 Update signed manifest to 7.40.0 2026-09-17 10:42:49 +00:00
tech f787d3a070 Update signed manifest to 7.39.9 2026-09-16 22:55:42 +00:00
tech e924c0adf1 Update signed manifest to 7.39.8 2026-09-16 22:24:00 +00:00
tech 343642a869 Update signed manifest to 7.39.7 2026-09-16 21:32:34 +00:00
tech 2cc4a318ad Update signed manifest to 7.39.6 2026-09-16 21:29:47 +00:00
tech e016046e1f Update signed manifest to 7.39.4 2026-09-16 20:32:22 +00:00
tech 0f197d4bfd Update signed manifest to 7.39.3 2026-09-16 18:30:54 +00:00
tech 49610d6da0 Update signed manifest to 7.39.2 2026-09-16 18:06:20 +00:00
tech 39463e255c Update signed manifest to 7.39.1 2026-09-16 13:42:30 +00:00
tech ecfe29e693 Update signed manifest to 7.39.0 2026-09-16 08:17:24 +00:00
tech 488b27ffc4 Update signed manifest to 7.38.0 2026-09-15 16:58:37 +00:00
tech 19246c3f5a Update signed manifest to 7.37.1 2026-09-15 16:45:42 +00:00
tech fddf34cd50 Update signed manifest to 7.37.0 2026-09-15 13:21:26 +00:00
tech db1f6d1fd6 Update signed manifest to 7.36.0 2026-09-15 11:03:26 +00:00
tech 5f928ba8d1 Update signed manifest to 7.35.0 2026-09-15 10:48:24 +00:00
tech 6a7b33b145 Update signed manifest to 7.34.0 2026-09-15 09:39:49 +00:00
tech 19ca566549 Update signed manifest to 7.33.0 2026-09-15 09:00:19 +00:00
tech bffd79abdb Update signed manifest to 7.32.0 2026-09-15 07:59:24 +00:00
tech c6faa3e74e Update signed manifest to 7.31.0 2026-09-15 00:51:19 +00:00
tech 022d999dc3 Update signed manifest to 7.30.0 2026-09-14 20:03:16 +00:00
tech 23e68f7385 Update signed manifest to 7.29.4 2026-09-14 14:57:40 +00:00
tech 51dcf25e5a Update signed manifest to 7.29.2 2026-09-14 09:33:46 +00:00
tech 50b657b23b Update signed manifest to 7.29.1 2026-09-14 09:20:05 +00:00
tech b7646b720a Update signed manifest to 7.29.0 2026-09-10 15:52:11 +00:00
tech 499a74da7b Update signed manifest to 7.28.6 2026-09-10 15:41:14 +00:00
tech 5028dfabd5 Update signed manifest to 7.28.5 2026-09-02 16:09:40 +00:00
tech daaff5e873 Update signed manifest to 7.28.4 2026-09-02 15:15:17 +00:00
tech 07c8997e1f Update signed manifest to 7.28.3 2026-08-26 09:17:13 +00:00
tech 2bf328fa3d Update signed manifest to 7.28.2 2026-08-16 07:50:50 +00:00
tech 5462c91106 Update signed manifest to 7.28.1 2026-08-16 07:25:22 +00:00
tech bb36fe77e0 Update signed manifest to 7.28.0 2026-08-15 21:32:33 +00:00
tech 1e1dfae997 Update signed manifest to 7.27.3 2026-08-14 18:07:43 +00:00
tech 5a1901fd3e Update signed manifest to 7.27.2 2026-08-14 17:28:45 +00:00
tech 10e92f4bd3 Update signed manifest to 7.27.1 2026-08-14 14:43:12 +00:00
tech 96aa3b20ea Update signed manifest to 7.27.0 2026-08-14 14:30:38 +00:00
tech e6f74fe0c3 Update signed manifest to 7.26.0 2026-08-14 13:59:45 +00:00
tech cd12b06c7a Update signed manifest to 7.25.7 2026-08-14 13:37:04 +00:00
tech d324d5a7a6 Update signed manifest to 7.25.6 2026-08-14 13:28:28 +00:00
tech b5e86863ba Update signed manifest to 7.25.5 2026-08-14 13:14:46 +00:00
tech cfe2cd5369 Update signed manifest to 7.25.4 2026-08-14 13:05:23 +00:00
tech c8e65d8b79 Update signed manifest to 7.25.3 2026-08-14 12:51:13 +00:00
tech 1f306340d8 Update signed manifest to 7.25.2 2026-08-14 12:26:58 +00:00
tech db095bc4a0 Update signed manifest to 7.25.1 2026-08-14 12:17:37 +00:00
tech 6031ba5163 Update signed manifest to 7.25.0 2026-08-14 10:32:57 +00:00
tech 7f7c8de38c Update signed manifest to 7.24.9 2026-08-14 10:15:10 +00:00
tech 769f532207 Update signed manifest to 7.24.8 2026-08-14 09:59:07 +00:00
tech cce638adb3 Update signed manifest to 7.24.6 2026-08-14 09:46:33 +00:00
tech 446bd5deb9 Update signed manifest to 7.24.5 2026-08-14 09:44:00 +00:00
tech b70f9b0675 Update signed manifest to 7.24.4 2026-08-14 09:17:23 +00:00
tech 3b5b86162b Update signed manifest to 7.24.3 2026-08-14 08:56:24 +00:00
tech 49b9d8fca5 Update signed manifest to 7.24.2 2026-08-14 08:06:56 +00:00
tech 1187ac412c Update signed manifest to 7.23.55 2026-08-13 19:15:45 +00:00
tech c56b4e6e9d Update signed manifest to 7.23.54 2026-08-13 12:58:44 +00:00
tech 6676ceb60e Update signed manifest to 7.23.53 2026-08-13 12:16:31 +00:00
tech fdc592d01f Update signed manifest to 7.23.52 2026-08-13 11:34:47 +00:00
tech 6e80e24159 Update signed manifest to 7.23.51 2026-08-13 11:09:50 +00:00
tech facfffbc1d Update signed manifest to 7.23.50 2026-08-13 11:00:49 +00:00
tech d33ab11b77 Update signed manifest to 7.23.49 2026-08-13 10:53:46 +00:00
tech 1bb03b6c5c Update signed manifest to 7.23.48 2026-08-13 10:52:36 +00:00
tech 729c24a83c Update signed manifest to 7.23.47 2026-08-13 09:49:16 +00:00
tech e95bc58ffc Update signed manifest to 7.23.46 2026-08-13 09:46:06 +00:00
tech 7cb4c1a8fc Update signed manifest to 7.23.45 2026-08-13 09:44:46 +00:00
tech f20c35fbc5 Update signed manifest to 7.23.44 2026-08-13 09:41:29 +00:00
tech d845e6a57d Update signed manifest to 7.23.43 2026-08-12 22:47:23 +00:00
tech fbfa908695 Update signed manifest to 7.23.42 2026-08-12 19:23:41 +00:00
tech 39ef0c2f3f Update signed manifest to 7.23.41 2026-08-12 19:11:53 +00:00
tech 9a459b4644 Update signed manifest to 7.23.40 2026-08-12 18:15:10 +00:00
tech 342c2ee43f Update signed manifest to 7.23.39 2026-08-12 17:58:06 +00:00
tech 105abf4e24 Update signed manifest to 7.23.38 2026-08-12 17:08:28 +00:00
tech d48beb9757 Update signed manifest to 7.23.37 2026-08-12 16:47:29 +00:00
tech 4a43572b15 Update signed manifest to 7.23.36 2026-08-12 15:43:51 +00:00
tech 20c46d3d26 Update signed manifest to 7.23.35 2026-08-12 15:40:08 +00:00
tech 74fdd42561 Update signed manifest to 7.23.34 2026-08-12 15:34:18 +00:00
tech 1e4806457e Update signed manifest to 7.23.33 2026-08-12 15:28:05 +00:00
tech da9237171a Update signed manifest to 7.23.32 2026-08-12 15:26:38 +00:00
tech 904bf52960 Update signed manifest to 7.23.31 2026-08-12 15:21:12 +00:00
tech 8ea7b9f5be Update signed manifest to 7.23.30 2026-08-12 13:24:01 +00:00
tech 804a1aae58 Update signed manifest to 7.23.28 2026-08-10 22:09:40 +00:00
tech 76e585139e Update signed manifest to 7.23.27 2026-08-10 20:18:36 +00:00
tech f871592d27 Update signed manifest to 7.23.26 2026-08-10 19:56:33 +00:00
tech f1375d3d7d Update signed manifest to 7.23.25 2026-08-10 19:23:48 +00:00
tech 72fa8e8901 Update signed manifest to 7.23.24 2026-08-10 16:35:02 +00:00
tech efa84723ee Update signed manifest to 7.23.23 2026-08-10 16:06:43 +00:00
tech 39616478c9 Update signed manifest to 7.23.22 2026-08-10 15:57:46 +00:00
tech c1969d4293 Update signed manifest to 7.23.21 2026-08-10 15:53:45 +00:00
tech 524b893e65 Update signed manifest to 7.23.20 2026-08-10 15:34:44 +00:00
tech 303f31e869 Update signed manifest to 7.23.19 2026-08-10 15:05:44 +00:00
tech 20a2a84ee8 Update signed manifest to 7.23.18 2026-08-10 14:14:25 +00:00
tech 9127b06836 Update signed manifest to 7.23.17 2026-08-10 11:36:11 +00:00
tech 19cb788556 Update signed manifest to 7.23.16 2026-08-10 11:08:03 +00:00
tech e52046437b Update signed manifest to 7.23.15 2026-08-10 10:55:14 +00:00
tech 5d36e1058f Update signed manifest to 7.23.14 2026-08-10 10:42:30 +00:00
tech b79ead3dcf Update signed manifest to 7.23.13 2026-08-10 09:48:52 +00:00
root b0102ea518 Update signed manifest to 7.23.12 (critical fix) 2026-08-10 01:14:06 +00:00
root 1b8d7950f5 Update signed manifest to 7.23.11 2026-08-10 00:59:55 +00:00
root 56284b5d20 Update signed manifest to 7.23.10 2026-08-10 00:39:47 +00:00
ARGUS 037c2b8cc6 Update signed manifest to 7.23.9 2026-08-09 23:50:36 +00:00
ARGUS b1688a0e16 Update signed manifest to 7.23.8 2026-08-09 23:29:08 +00:00
ARGUS 6649b75eb9 Update signed manifest to 7.23.7 (critical fix) 2026-08-09 23:10:37 +00:00
ARGUS 99bce8b86a Update signed manifest to 7.23.6 2026-08-09 22:57:31 +00:00
ARGUS a214c1656f Update signed manifest to 7.23.5 2026-08-09 22:53:52 +00:00
ARGUS 1b1f657b41 Update signed manifest to 7.23.3 2026-08-09 20:06:34 +00:00
ARGUS af032f8f1c Update signed manifest to 7.23.2 2026-08-09 19:39:28 +00:00
ARGUSandClaude Sonnet 5 c4db87af84 ARGUS WordPress Defence 7.23.2
- New: License section in Settings -- a 30-day trial with every
  feature active starts automatically, no registration required to
  start. Protection continues regardless of license status; the trial
  only affects what's shown, never what's enforced. Enter a license
  key to keep Premium features once the trial ends. This is the first
  release where the license system is actually visible anywhere --
  it existed in the codebase before but had no entry form and was
  excluded from every published build.
- Intelligence page: rewrote descriptions in plain language about what
  each thing does for your site's protection, not internal
  implementation details (was showing raw technical descriptions like
  a "signature corpus" and "RIR delegation database" range counts).

Verified end-to-end before publishing (not just code review): a real
signup and license created on the actual companion license service,
then submitted through the real Settings page form exactly as a
customer would -- status went from "TRIAL -- 30 DAYS LEFT" to
"LICENSED" on both the Settings and Overview pages. This exact ZIP was
also installed fresh and every admin page loaded with zero errors
before this commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 19:37:16 +00:00
ARGUSandClaude Sonnet 5 8a6c9ca4de ARGUS WordPress Defence 7.23.1 -- real, working automatic updates
Fixes a real bug: a normal (non-critical) update was previously
detected and shown as available, but nothing ever actually installed
it -- there was no button, cron path, or any other code that did.
Automatic updates only ever worked for updates flagged critical, which
isn't what "automatic" means. Now any newer, compatible,
signature-verified update installs on its own.

This is also the first release where the self-update mechanism itself
ships in this self-distributed channel -- it was unconditionally
excluded from every previous build (a WordPress.org-only restriction
that doesn't apply here, since this channel isn't WordPress.org).

manifest.json in this repo is the real, live update manifest: signed
with Ed25519 (public key documented in README.md's Updating section),
pointing at this exact release's ZIP and its real SHA-256. Verified
end-to-end before publishing -- not just "the code looks right": ran a
full real update cycle (an older installed version checking this
manifest, downloading this exact package, verifying its signature and
hash, replacing itself, and the site continuing to work with zero
errors afterward) using the actual signing key and the actual
package this commit ships.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 19:19:40 +00:00
ARGUSandClaude Sonnet 5 35efd3e485 ARGUS WordPress Defence 7.23.0
Real changes since 1.0.0, all live-verified before this release:

- Firewall: rule corpus expanded 19 -> 43 rules, real OWASP-CRS-equivalent
  coverage (XXE, SSRF, session fixation, Log4Shell/JNDI, scanner-tool
  detection, deeper SQL injection/XSS/PHP-injection).
- Fixed a real bug: a quarantined file's severity badge and its content
  analysis score could disagree with no explanation (e.g. a benign file
  showing CRITICAL next to Score 0); both are now derived consistently
  and shown together.
- ARGUS now always keeps itself updated, and can optionally do the same
  for every other installed plugin and theme (Settings, on by default) --
  uses WordPress's own native update system, nothing custom.
- Global Threat Intelligence is now opt-in, not automatic -- a single
  click on its own page, with an honest, specific description of exactly
  what's shared (an IP address, a reason code, a confidence score, a
  country). Previously connected automatically on activation.
- New first-run Welcome screen after activation: confirms what's already
  protecting the site, and surfaces the few real optional choices in one
  place.
- Dashboard: running version now visible in the header; new "IPs
  Tracked" and "ANIS Protections" metrics.
- Full WordPress.org Plugin Directory readiness audit performed against
  this codebase. Two real compliance issues found and fixed (see above:
  Global Threat Intelligence's default, and the self-update mechanism,
  which is excluded from this build entirely -- WordPress.org prohibits
  a plugin from using any update channel other than its own, even an
  inert one). This release is still self-distributed, not a WordPress.org
  submission -- that remains a future step.

Verified before publishing: this exact ZIP was installed, activated (14
admin pages loaded clean, zero PHP errors/warnings), and uninstalled
(zero leftover database tables or options) in a fresh, disposable
WordPress + MySQL environment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 19:08:02 +00:00
tech 35742e05ab README: state testing/audit/CI status explicitly
No behavior or code change -- documentation only. Makes explicit what
was previously only implied: self-distributed (not on WordPress.org),
not independently security audited, no CI pipeline yet, tested on
disposable WordPress environments rather than live production sites,
and automatic updates are inactive pending real update infrastructure.
2026-08-09 13:44:47 +00:00
25 changed files with 979 additions and 93 deletions
+57 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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'; ?>
+19 -31
View File
@@ -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>
+42
View File
@@ -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' ); ?> &middot; <?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'];
?>
+1
View File
@@ -48,6 +48,7 @@ $argus_nav_items = array(
<?php echo esc_html( $argus_protection_display['label'] ); ?>
&middot; <?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' ); ?>">&#9881;</a>
</div>
+48 -1
View File
@@ -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>
+139
View File
@@ -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'; ?>
+2 -2
View File
@@ -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__ ) );
+21
View 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
View File
@@ -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
+81
View File
@@ -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";
+14
View File
@@ -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() {
+4 -1
View File
@@ -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';
}
+31 -7
View File
@@ -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' ),
);
}
}
+5
View File
@@ -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 ) );
}
+127
View File
@@ -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 : '',
);
}
}
+59 -10
View File
@@ -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;
}
+17 -1
View File
@@ -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 );
+13 -1
View File
@@ -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;
+10 -1
View File
@@ -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' => '',
+9 -6
View File
@@ -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 ) {
+86 -6
View File
@@ -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;
+11
View File
@@ -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
View File
@@ -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.