Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a6c9ca4de |
@@ -4,7 +4,7 @@ Automatic WordPress security. A local firewall, malware and file-integrity scann
|
|||||||
vulnerability protection, and scheduled backups — protecting your site with zero manual
|
vulnerability protection, and scheduled backups — protecting your site with zero manual
|
||||||
configuration required.
|
configuration required.
|
||||||
|
|
||||||
**Version:** 7.23.0
|
**Version:** 7.23.1
|
||||||
**Requires:** WordPress 6.0+, PHP 7.4+
|
**Requires:** WordPress 6.0+, PHP 7.4+
|
||||||
**License:** GPLv2 or later
|
**License:** GPLv2 or later
|
||||||
|
|
||||||
@@ -43,11 +43,12 @@ release is not it).
|
|||||||
- **Tested in sandbox/disposable WordPress environments.** Verification so far — install,
|
- **Tested in sandbox/disposable WordPress environments.** Verification so far — install,
|
||||||
activation, all admin pages, deactivation, uninstall — was done on disposable WordPress
|
activation, all admin pages, deactivation, uninstall — was done on disposable WordPress
|
||||||
instances, not on live production sites.
|
instances, not on live production sites.
|
||||||
- **Automatic updates for ARGUS itself are not currently active.** The signed-update
|
- **Automatic updates for ARGUS itself are opt-in, not on by default.** The signed-update
|
||||||
mechanism is built in but requires production update infrastructure (a manifest server
|
mechanism is real and live (this release's own manifest is signed and hosted in this
|
||||||
and signing key) that is not yet deployed. See [Updating](#updating) below for how to
|
repository) -- add two constants to `wp-config.php` to turn it on. See
|
||||||
update manually. Automatic updates for *other* plugins/themes on your site, if you enable
|
[Updating](#updating) below for the exact lines and how to verify it's working.
|
||||||
that option, use WordPress's own built-in update system and work today.
|
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
|
## Installation
|
||||||
|
|
||||||
@@ -63,11 +64,23 @@ sidebar with an overview of your site's protection status.
|
|||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
||||||
Automatic in-dashboard updates require an update channel to be configured for your
|
**Automatic** — add these two lines to `wp-config.php` (above the
|
||||||
deployment (`ARGUS_WPD_UPDATE_MANIFEST_URL` and `ARGUS_WPD_UPDATE_PUBLIC_KEY` in
|
`/* That's all, stop editing! */` line):
|
||||||
`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 →
|
```php
|
||||||
Upload Plugin** — WordPress will offer to replace the existing version.
|
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
|
## Uninstalling
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: ARGUS WordPress Defence
|
* Plugin Name: ARGUS WordPress Defence
|
||||||
* Plugin URI: https://git-cloud.weboria.eu/Weboria/argus-wp-defence
|
* Plugin URI: https://git-cloud.weboria.eu/Weboria/argus-wp-defence
|
||||||
* Description: Standalone WordPress security: local WAF, malware & integrity scanning, vulnerability intelligence, and a deterministic ban/policy engine. Works fully offline; optionally connects to ARGUS Cloud for richer intelligence and cross-asset correlation.
|
* Description: Standalone WordPress security: local WAF, malware & integrity scanning, vulnerability intelligence, and a deterministic ban/policy engine. Works fully offline; optionally connects to ARGUS Cloud for richer intelligence and cross-asset correlation.
|
||||||
* Version: 7.23.0
|
* Version: 7.23.1
|
||||||
* Requires at least: 6.0
|
* Requires at least: 6.0
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 7.4
|
||||||
* Author: ARGUS
|
* Author: ARGUS
|
||||||
@@ -28,7 +28,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define( 'ARGUS_WPD_VERSION', '7.23.0' );
|
define( 'ARGUS_WPD_VERSION', '7.23.1' );
|
||||||
define( 'ARGUS_WPD_FILE', __FILE__ );
|
define( 'ARGUS_WPD_FILE', __FILE__ );
|
||||||
define( 'ARGUS_WPD_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'ARGUS_WPD_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'ARGUS_WPD_URL', plugin_dir_url( __FILE__ ) );
|
define( 'ARGUS_WPD_URL', plugin_dir_url( __FILE__ ) );
|
||||||
|
|||||||
+21
-8
@@ -8,12 +8,20 @@
|
|||||||
# the result against an explicit allowlist before packaging, failing
|
# the result against an explicit allowlist before packaging, failing
|
||||||
# closed on anything unexpected rather than silently shipping it.
|
# 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
|
set -euo pipefail
|
||||||
|
|
||||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
REF="${1:-HEAD}"
|
REF="${1:-HEAD}"
|
||||||
|
CHANNEL="${2:-self}"
|
||||||
BUILD_DIR="$(mktemp -d)"
|
BUILD_DIR="$(mktemp -d)"
|
||||||
STAGE_DIR="${BUILD_DIR}/argus-wordpress-defence"
|
STAGE_DIR="${BUILD_DIR}/argus-wordpress-defence"
|
||||||
OUT_DIR="${REPO_ROOT}/dist"
|
OUT_DIR="${REPO_ROOT}/dist"
|
||||||
@@ -35,13 +43,18 @@ rm -rf \
|
|||||||
"${STAGE_DIR}/.gitignore" \
|
"${STAGE_DIR}/.gitignore" \
|
||||||
"${STAGE_DIR}/bin"
|
"${STAGE_DIR}/bin"
|
||||||
|
|
||||||
# A plugin hosted on WordPress.org must rely solely on WordPress.org's own
|
if [ "${CHANNEL}" = "wporg" ]; then
|
||||||
# update channel -- never a self-update-from-external-manifest mechanism,
|
# A plugin hosted on WordPress.org must rely solely on WordPress.org's own
|
||||||
# even one that ships inert-by-default. class_exists( 'Argus_Update_Client' )
|
# update channel -- never a self-update-from-external-manifest mechanism,
|
||||||
# guards every reference to this class elsewhere in the codebase, so removing
|
# even one that ships inert-by-default. class_exists( 'Argus_Update_Client' )
|
||||||
# the file here is sufficient; nothing else needs to change per build.
|
# guards every reference to this class elsewhere in the codebase, so
|
||||||
echo "==> Removing the self-update client (WordPress.org must be the only update channel)"
|
# removing the file here is sufficient; nothing else needs to change.
|
||||||
rm -f "${STAGE_DIR}/includes/class-argus-update-client.php"
|
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"
|
||||||
|
elif [ "${CHANNEL}" != "self" ]; then
|
||||||
|
echo "==> BUILD FAILED: unknown channel '${CHANNEL}' (expected 'self' or 'wporg')"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# WordPress.org explicitly prohibits trialware -- a plugin submitted to the
|
# WordPress.org explicitly prohibits trialware -- a plugin submitted to the
|
||||||
# directory must stay genuinely, permanently functional with no license
|
# directory must stay genuinely, permanently functional with no license
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Argus_Update_Client {
|
||||||
|
|
||||||
|
const CHECK_INTERVAL_SECS = 6 * HOUR_IN_SECONDS;
|
||||||
|
const LOCK_TRANSIENT = 'argus_wpd_update_check_lock';
|
||||||
|
|
||||||
|
const LAST_CHECK_OPTION = 'argus_wpd_update_last_check';
|
||||||
|
const MANIFEST_ETAG_OPTION = 'argus_wpd_update_manifest_etag';
|
||||||
|
const PENDING_MANIFEST_OPTION = 'argus_wpd_update_pending_manifest';
|
||||||
|
const LAST_SUCCESS_OPTION = 'argus_wpd_update_last_success';
|
||||||
|
const LAST_FAILURE_OPTION = 'argus_wpd_update_last_failure';
|
||||||
|
|
||||||
|
public static function manifest_url() {
|
||||||
|
return defined( 'ARGUS_WPD_UPDATE_MANIFEST_URL' ) ? ARGUS_WPD_UPDATE_MANIFEST_URL : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function public_key() {
|
||||||
|
return defined( 'ARGUS_WPD_UPDATE_PUBLIC_KEY' ) ? ARGUS_WPD_UPDATE_PUBLIC_KEY : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function is_configured() {
|
||||||
|
return '' !== self::manifest_url() && '' !== self::public_key();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function maybe_check() {
|
||||||
|
if ( ! self::is_configured() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$last_check = (int) get_option( self::LAST_CHECK_OPTION, 0 );
|
||||||
|
if ( ( $last_check + self::CHECK_INTERVAL_SECS ) > time() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( false !== get_transient( self::LOCK_TRANSIENT ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
set_transient( self::LOCK_TRANSIENT, 1, 4 * MINUTE_IN_SECONDS );
|
||||||
|
|
||||||
|
self::check_now();
|
||||||
|
|
||||||
|
delete_transient( self::LOCK_TRANSIENT );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function check_now() {
|
||||||
|
if ( ! self::is_configured() ) {
|
||||||
|
return array( 'success' => false, 'message' => __( 'No update source is configured.', 'argus-wordpress-defence' ), 'update_available' => false );
|
||||||
|
}
|
||||||
|
|
||||||
|
update_option( self::LAST_CHECK_OPTION, time(), false );
|
||||||
|
|
||||||
|
$args = array( 'timeout' => 15 );
|
||||||
|
$etag = get_option( self::MANIFEST_ETAG_OPTION, '' );
|
||||||
|
if ( $etag ) {
|
||||||
|
$args['headers'] = array( 'If-None-Match' => $etag );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = wp_remote_get( self::manifest_url(), $args );
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return self::record_failure( __( 'Could not reach the update server.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = wp_remote_retrieve_response_code( $response );
|
||||||
|
if ( 304 === $code ) {
|
||||||
|
|
||||||
|
return array( 'success' => true, 'message' => '', 'update_available' => false );
|
||||||
|
}
|
||||||
|
if ( 200 !== $code ) {
|
||||||
|
return self::record_failure( sprintf( 'Update server returned HTTP %d.', $code ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_etag = wp_remote_retrieve_header( $response, 'etag' );
|
||||||
|
if ( $new_etag ) {
|
||||||
|
update_option( self::MANIFEST_ETAG_OPTION, $new_etag, false );
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = wp_remote_retrieve_body( $response );
|
||||||
|
$manifest = json_decode( $body, true );
|
||||||
|
if ( ! is_array( $manifest ) ) {
|
||||||
|
return self::record_failure( __( 'Update manifest was not valid JSON.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::process_manifest( $manifest );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function process_manifest( array $manifest ) {
|
||||||
|
|
||||||
|
if ( ! self::verify_signature( $manifest ) ) {
|
||||||
|
return self::record_failure( __( 'Update manifest failed signature verification.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = $manifest['version'] ?? '';
|
||||||
|
if ( '' === $version ) {
|
||||||
|
return self::record_failure( __( 'Update manifest is missing a version.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( version_compare( $version, ARGUS_WPD_VERSION, '<=' ) ) {
|
||||||
|
return array( 'success' => true, 'message' => '', 'update_available' => false );
|
||||||
|
}
|
||||||
|
|
||||||
|
$last_installed = get_option( self::LAST_SUCCESS_OPTION, array() );
|
||||||
|
if ( ! empty( $last_installed['version'] ) && version_compare( $version, $last_installed['version'], '<=' ) ) {
|
||||||
|
return array( 'success' => true, 'message' => '', 'update_available' => false );
|
||||||
|
}
|
||||||
|
|
||||||
|
global $wp_version;
|
||||||
|
if ( ! empty( $manifest['min_php'] ) && version_compare( PHP_VERSION, $manifest['min_php'], '<' ) ) {
|
||||||
|
return self::record_failure( sprintf( 'Update %s requires PHP %s or newer.', $version, $manifest['min_php'] ) );
|
||||||
|
}
|
||||||
|
if ( ! empty( $manifest['min_wp'] ) && version_compare( $wp_version, $manifest['min_wp'], '<' ) ) {
|
||||||
|
return self::record_failure( sprintf( 'Update %s requires WordPress %s or newer.', $version, $manifest['min_wp'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
update_option( self::PENDING_MANIFEST_OPTION, $manifest, false );
|
||||||
|
|
||||||
|
// 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 ) {
|
||||||
|
if ( empty( $manifest['signature'] ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$signature = base64_decode( $manifest['signature'], true );
|
||||||
|
if ( false === $signature || SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$public_key_b64 = self::public_key();
|
||||||
|
if ( '' === $public_key_b64 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$public_key = base64_decode( $public_key_b64, true );
|
||||||
|
if ( false === $public_key ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$canonical = self::canonical_payload( $manifest );
|
||||||
|
return sodium_crypto_sign_verify_detached( $signature, $canonical, $public_key );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function canonical_payload( array $manifest ) {
|
||||||
|
$ordered = array();
|
||||||
|
foreach ( array( 'version', 'released_at', 'package_url', 'sha256', 'min_php', 'min_wp', 'critical' ) as $key ) {
|
||||||
|
$ordered[ $key ] = $manifest[ $key ] ?? null;
|
||||||
|
}
|
||||||
|
return wp_json_encode( $ordered, JSON_UNESCAPED_SLASHES );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function install_update( array $manifest, $target_dir = null ) {
|
||||||
|
$target_dir = $target_dir ?: ( WP_PLUGIN_DIR . '/argus-wordpress-defence' );
|
||||||
|
$version = $manifest['version'];
|
||||||
|
|
||||||
|
$upgrade_dir = trailingslashit( wp_get_upload_dir()['basedir'] ) . 'argus-wpd-data/update-staging';
|
||||||
|
if ( ! is_dir( $upgrade_dir ) && ! wp_mkdir_p( $upgrade_dir ) ) {
|
||||||
|
return self::record_failure( __( 'Could not prepare the update staging directory.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$package_path = trailingslashit( $upgrade_dir ) . 'package-' . $version . '-' . bin2hex( random_bytes( 4 ) ) . '.zip';
|
||||||
|
$response = wp_remote_get( $manifest['package_url'], array( 'timeout' => 120, 'stream' => true, 'filename' => $package_path ) );
|
||||||
|
if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
|
||||||
|
@unlink( $package_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors, WordPress.WP.AlternativeFunctions
|
||||||
|
return self::record_failure( __( 'Could not download the update package.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! hash_equals( $manifest['sha256'], hash_file( 'sha256', $package_path ) ) ) {
|
||||||
|
wp_delete_file( $package_path );
|
||||||
|
return self::record_failure( __( 'Downloaded package hash did not match the signed manifest -- rejected.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$extract_root = trailingslashit( $upgrade_dir ) . 'extract-' . $version . '-' . bin2hex( random_bytes( 4 ) );
|
||||||
|
if ( ! class_exists( 'ZipArchive' ) ) {
|
||||||
|
wp_delete_file( $package_path );
|
||||||
|
return self::record_failure( __( 'The PHP zip extension is not available -- cannot install the update.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if ( true !== $zip->open( $package_path ) || true !== $zip->extractTo( $extract_root ) ) {
|
||||||
|
wp_delete_file( $package_path );
|
||||||
|
return self::record_failure( __( 'Could not extract the update package.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
$zip->close();
|
||||||
|
wp_delete_file( $package_path );
|
||||||
|
|
||||||
|
$extracted_plugin_dir = trailingslashit( $extract_root ) . 'argus-wordpress-defence';
|
||||||
|
$new_main_file = trailingslashit( $extracted_plugin_dir ) . 'argus-wordpress-defence.php';
|
||||||
|
if ( ! file_exists( $new_main_file ) ) {
|
||||||
|
self::rrmdir( $extract_root );
|
||||||
|
return self::record_failure( __( 'Extracted package did not contain the expected plugin file.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$header_contents = file_get_contents( $new_main_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||||
|
if ( ! preg_match( '/Version:\s*([^\r\n]+)/', $header_contents, $m ) || trim( $m[1] ) !== $version ) {
|
||||||
|
self::rrmdir( $extract_root );
|
||||||
|
return self::record_failure( __( 'Extracted package version did not match the signed manifest -- rejected.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$backup_dir = $target_dir . '-previous-' . time();
|
||||||
|
$had_previous = is_dir( $target_dir );
|
||||||
|
if ( $had_previous && ! rename( $target_dir, $backup_dir ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||||
|
self::rrmdir( $extract_root );
|
||||||
|
return self::record_failure( __( 'Could not move the current plugin version aside for the update.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
if ( ! rename( $extracted_plugin_dir, $target_dir ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||||
|
|
||||||
|
if ( $had_previous ) {
|
||||||
|
rename( $backup_dir, $target_dir ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||||
|
}
|
||||||
|
self::rrmdir( $extract_root );
|
||||||
|
return self::record_failure( __( 'Could not activate the downloaded update -- rolled back to the previous version.', 'argus-wordpress-defence' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
self::rrmdir( $extract_root );
|
||||||
|
if ( $had_previous ) {
|
||||||
|
self::rrmdir( $backup_dir );
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_option( self::PENDING_MANIFEST_OPTION );
|
||||||
|
update_option(
|
||||||
|
self::LAST_SUCCESS_OPTION,
|
||||||
|
array( 'version' => $version, 'installed_at' => current_time( 'mysql', true ) ),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'success' => true,
|
||||||
|
'message' => ! empty( $manifest['critical'] )
|
||||||
|
? sprintf( __( 'A critical ARGUS security update was installed (%s).', 'argus-wordpress-defence' ), $version )
|
||||||
|
: sprintf( __( 'ARGUS Defence was updated to version %s.', 'argus-wordpress-defence' ), $version ),
|
||||||
|
'update_available' => false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function record_failure( $message ) {
|
||||||
|
update_option(
|
||||||
|
self::LAST_FAILURE_OPTION,
|
||||||
|
array( 'message' => $message, 'at' => current_time( 'mysql', true ) ),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
Argus_Events::record( 'update_check_failed', 'medium', $message, array() );
|
||||||
|
return array( 'success' => false, 'message' => $message, 'update_available' => false );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function rrmdir( $dir ) {
|
||||||
|
if ( ! is_dir( $dir ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$items = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST );
|
||||||
|
foreach ( $items as $item ) {
|
||||||
|
$item->isDir() ? rmdir( $item->getPathname() ) : unlink( $item->getPathname() ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||||
|
}
|
||||||
|
rmdir( $dir ); // phpcs:ignore WordPress.WP.AlternativeFunctions
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function status() {
|
||||||
|
return array(
|
||||||
|
'current_version' => ARGUS_WPD_VERSION,
|
||||||
|
'configured' => self::is_configured(),
|
||||||
|
'last_check' => get_option( self::LAST_CHECK_OPTION, 0 ),
|
||||||
|
'last_success' => get_option( self::LAST_SUCCESS_OPTION, array() ),
|
||||||
|
'last_failure' => get_option( self::LAST_FAILURE_OPTION, array() ),
|
||||||
|
'pending' => get_option( self::PENDING_MANIFEST_OPTION, array() ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"version": "7.23.1",
|
||||||
|
"released_at": "2026-08-09T19:17:57+00:00",
|
||||||
|
"package_url": "https://git-cloud.weboria.eu/Weboria/argus-wp-defence/releases/download/v7.23.1/argus-wordpress-defence-7.23.1.zip",
|
||||||
|
"sha256": "22e4e75f6ea0933787a8e3f63c9c8f99da97df36529354b116af730eaa34c38a",
|
||||||
|
"min_php": "7.4",
|
||||||
|
"min_wp": "6.0",
|
||||||
|
"critical": false,
|
||||||
|
"signature": "68j0L5KMYY+10s9NmhQCeNr80+13ZMs+fj8ynW3ov9Nst64+Wc+e1nqE3Y0TlNKBj5fHqty1Fz0HBT14pXcwCg=="
|
||||||
|
}
|
||||||
+6
-1
@@ -4,7 +4,7 @@ Tags: security, firewall, malware, vulnerability, backup
|
|||||||
Requires at least: 6.0
|
Requires at least: 6.0
|
||||||
Tested up to: 7.0
|
Tested up to: 7.0
|
||||||
Requires PHP: 7.4
|
Requires PHP: 7.4
|
||||||
Stable tag: 7.23.0
|
Stable tag: 7.23.1
|
||||||
License: GPLv2 or later
|
License: GPLv2 or later
|
||||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
|
||||||
@@ -53,6 +53,11 @@ dashboard whenever a new version is available, and you can update it in one clic
|
|||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 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 =
|
= 7.23.0 =
|
||||||
* Firewall: broadened local rule coverage (XXE, SSRF, session fixation, Log4Shell/JNDI, scanner-tool
|
* Firewall: broadened local rule coverage (XXE, SSRF, session fixation, Log4Shell/JNDI, scanner-tool
|
||||||
detection, and deeper SQL injection / XSS / PHP injection signatures).
|
detection, and deeper SQL injection / XSS / PHP injection signatures).
|
||||||
|
|||||||
Reference in New Issue
Block a user