Files
argus-wp-defence/includes/class-argus-db.php
T
root df0f2fccb8 ARGUS WordPress Defence 1.0.0 — first production release
Automatic WordPress security: local firewall, malware and file-integrity
scanning, vulnerability protection, quarantine, scheduled backups, an
optional page cache, and automatic global threat intelligence.

See README.md for installation, update, and uninstall instructions.
2026-08-09 13:40:16 +00:00

312 lines
9.7 KiB
PHP

<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Argus_DB {
const SCHEMA_VERSION = '8';
const SCHEMA_OPTION = 'argus_wpd_schema_version';
protected static function drop_index_if_exists( $table, $index ) {
global $wpdb;
$exists = $wpdb->get_var(
$wpdb->prepare( 'SHOW INDEX FROM ' . $table . ' WHERE Key_name = %s', $index ) // phpcs:ignore
);
if ( $exists ) {
$wpdb->query( 'ALTER TABLE ' . $table . ' DROP INDEX ' . $index ); // phpcs:ignore
}
}
public static function table( $name ) {
global $wpdb;
return $wpdb->prefix . 'argus_' . $name;
}
public static function install() {
global $wpdb;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$charset_collate = $wpdb->get_charset_collate();
$events = self::table( 'events' );
$findings = self::table( 'findings' );
$bans = self::table( 'bans' );
$policy_decisions = self::table( 'policy_decisions' );
$integrity_baseline = self::table( 'integrity_baseline' );
$login_attempts = self::table( 'login_attempts' );
$vuln_cache = self::table( 'vuln_cache' );
$quarantine = self::table( 'quarantine' );
$cache_stats_daily = self::table( 'cache_stats_daily' );
$cache_discovered_urls = self::table( 'cache_discovered_urls' );
$cache_warm_queue = self::table( 'cache_warm_queue' );
$anis_reputation = self::table( 'anis_reputation' );
$vuln_status = self::table( 'vuln_status' );
$scan_history = self::table( 'scan_history' );
$backups = self::table( 'backups' );
$sql = "CREATE TABLE {$events} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
event_type VARCHAR(40) NOT NULL,
severity VARCHAR(20) NOT NULL DEFAULT 'info',
ip VARCHAR(45) NULL,
summary VARCHAR(255) NOT NULL,
payload LONGTEXT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY event_type (event_type),
KEY created_at (created_at),
KEY ip (ip)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$findings} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
category VARCHAR(40) NOT NULL,
severity VARCHAR(20) NOT NULL,
title VARCHAR(255) NOT NULL,
detail LONGTEXT NULL,
remediation_hint LONGTEXT NULL,
evidence LONGTEXT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'open',
first_seen_at DATETIME NOT NULL,
last_seen_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY category (category),
KEY status (status),
KEY severity (severity)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$bans} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ip VARCHAR(45) NOT NULL,
source VARCHAR(40) NOT NULL,
reason VARCHAR(255) NOT NULL,
evidence LONGTEXT NULL,
ban_level VARCHAR(20) NOT NULL DEFAULT 'temporary',
created_at DATETIME NOT NULL,
expires_at DATETIME NULL,
recovery_eligible_at DATETIME NULL,
lifted_at DATETIME NULL,
PRIMARY KEY (id),
KEY ip (ip),
KEY expires_at (expires_at)
) {$charset_collate};";
dbDelta( $sql );
self::drop_index_if_exists( $bans, 'ip_active' );
$sql = "CREATE TABLE {$policy_decisions} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ip VARCHAR(45) NOT NULL,
trigger_type VARCHAR(40) NOT NULL,
action VARCHAR(20) NOT NULL,
rule_name VARCHAR(100) NULL,
reason VARCHAR(255) NOT NULL,
observation_only TINYINT(1) NOT NULL DEFAULT 0,
mode VARCHAR(20) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY ip (ip),
KEY created_at (created_at)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$integrity_baseline} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
file_path VARCHAR(500) NOT NULL,
file_hash CHAR(64) NOT NULL,
file_size BIGINT UNSIGNED NOT NULL,
file_mtime DATETIME NOT NULL,
category VARCHAR(20) NOT NULL,
first_seen_at DATETIME NOT NULL,
last_checked_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY file_path (file_path(191)),
KEY category (category)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$login_attempts} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ip VARCHAR(45) NOT NULL,
username VARCHAR(60) NOT NULL,
success TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY ip_created (ip, created_at),
KEY username_created (username, created_at)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$vuln_cache} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
component_type VARCHAR(20) NOT NULL,
slug VARCHAR(191) NOT NULL,
vulnerable_below VARCHAR(40) NULL,
fixed_in VARCHAR(40) NULL,
severity VARCHAR(20) NOT NULL,
cve VARCHAR(20) NULL,
description LONGTEXT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY component_slug (component_type, slug(120))
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$quarantine} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
original_path VARCHAR(500) NOT NULL,
quarantine_filename VARCHAR(60) NOT NULL,
original_filename VARCHAR(255) NOT NULL,
file_size BIGINT UNSIGNED NOT NULL,
file_hash CHAR(64) NOT NULL,
original_mtime DATETIME NULL,
original_perms VARCHAR(4) NULL,
detection_engine VARCHAR(60) NOT NULL,
detection_rule VARCHAR(100) NULL,
detection_type VARCHAR(20) NOT NULL,
severity VARCHAR(20) NOT NULL,
confidence_score INT NULL,
matched_rules LONGTEXT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'quarantined',
finding_id BIGINT UNSIGNED NULL,
quarantined_at DATETIME NOT NULL,
analysed_at DATETIME NULL,
resolved_at DATETIME NULL,
restore_attempts INT NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY quarantine_filename (quarantine_filename),
KEY status (status),
KEY finding_id (finding_id)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$cache_stats_daily} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
stat_date DATE NOT NULL,
status VARCHAR(20) NOT NULL,
request_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
total_response_time_ms BIGINT UNSIGNED NOT NULL DEFAULT 0,
total_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY date_status (stat_date, status)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$cache_discovered_urls} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
url VARCHAR(500) NOT NULL,
source VARCHAR(40) NOT NULL,
eligibility VARCHAR(20) NOT NULL DEFAULT 'needs_review',
eligibility_reason VARCHAR(255) NULL,
priority_hint DECIMAL(3,2) NULL,
lastmod DATETIME NULL,
last_checked_at DATETIME NULL,
last_warmed_at DATETIME NULL,
discovered_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY url (url(191)),
KEY eligibility (eligibility)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$cache_warm_queue} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
url VARCHAR(500) NOT NULL,
priority INT NOT NULL DEFAULT 100,
status VARCHAR(20) NOT NULL DEFAULT 'queued',
attempts INT NOT NULL DEFAULT 0,
last_attempt_at DATETIME NULL,
last_result VARCHAR(255) NULL,
queued_at DATETIME NOT NULL,
PRIMARY KEY (id),
KEY status_priority (status, priority),
KEY url (url(191))
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$anis_reputation} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
ip VARCHAR(45) NOT NULL,
action VARCHAR(20) NOT NULL,
confidence INT NOT NULL DEFAULT 0,
reports INT NOT NULL DEFAULT 0,
source VARCHAR(60) NULL,
reason VARCHAR(255) NULL,
decided_at DATETIME NULL,
expires_at DATETIME NULL,
cached_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY ip (ip),
KEY action (action)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$vuln_status} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
component_type VARCHAR(20) NOT NULL,
slug VARCHAR(191) NOT NULL,
name VARCHAR(255) NOT NULL,
installed_version VARCHAR(40) NULL,
checked_version VARCHAR(40) NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
severity VARCHAR(20) NULL,
cve VARCHAR(20) NULL,
fixed_in VARCHAR(40) NULL,
description LONGTEXT NULL,
last_checked_at DATETIME NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY component (component_type, slug),
KEY status (status)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$scan_history} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
scan_type VARCHAR(30) NOT NULL,
started_at DATETIME NOT NULL,
finished_at DATETIME NOT NULL,
files_scanned INT UNSIGNED NOT NULL DEFAULT 0,
findings_count INT UNSIGNED NOT NULL DEFAULT 0,
result VARCHAR(20) NOT NULL,
PRIMARY KEY (id),
KEY started_at (started_at)
) {$charset_collate};";
dbDelta( $sql );
$sql = "CREATE TABLE {$backups} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
filename VARCHAR(191) NOT NULL,
type VARCHAR(20) NOT NULL,
size_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'complete',
error_message TEXT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY filename (filename),
KEY created_at (created_at)
) {$charset_collate};";
dbDelta( $sql );
update_option( self::SCHEMA_OPTION, self::SCHEMA_VERSION, false );
}
public static function maybe_upgrade() {
if ( get_option( self::SCHEMA_OPTION ) !== self::SCHEMA_VERSION ) {
self::install();
}
}
public static function uninstall() {
global $wpdb;
foreach ( array( 'events', 'findings', 'bans', 'policy_decisions', 'integrity_baseline', 'login_attempts', 'vuln_cache', 'quarantine', 'cache_stats_daily', 'cache_discovered_urls', 'cache_warm_queue', 'anis_reputation', 'vuln_status', 'scan_history', 'backups' ) as $t ) {
$wpdb->query( 'DROP TABLE IF EXISTS ' . self::table( $t ) ); // phpcs:ignore
}
delete_option( self::SCHEMA_OPTION );
}
}