' . "\n"; } /** * Check if plugin is active on current page */ private static function is_plugin_active_on_page() { global $post; if (!is_a($post, 'WP_Post')) { return false; } return has_shortcode($post->post_content, 'maple_code_block'); } /** * Sanitize global input arrays */ public static function sanitize_global_input() { // Sanitize $_GET if (!empty($_GET)) { $_GET = self::sanitize_array($_GET); } // Sanitize $_POST if (!empty($_POST)) { $_POST = self::sanitize_array($_POST); } // Sanitize $_REQUEST if (!empty($_REQUEST)) { $_REQUEST = self::sanitize_array($_REQUEST); } // Sanitize $_COOKIE if (!empty($_COOKIE)) { $_COOKIE = self::sanitize_array($_COOKIE); } } /** * Recursively sanitize array */ private static function sanitize_array($array) { foreach ($array as $key => $value) { if (is_array($value)) { $array[$key] = self::sanitize_array($value); } else { // Remove null bytes $value = str_replace(chr(0), '', $value); // Strip tags and encode special chars $array[$key] = htmlspecialchars(strip_tags($value), ENT_QUOTES, 'UTF-8'); } } return $array; } /** * Validate repository format * Supports: owner/repo, platform:owner/repo, or full URLs */ public static function validate_repo_format($repo) { // Remove any whitespace $repo = trim($repo); // If it's a full URL, validate it if (strpos($repo, 'https://') === 0 || strpos($repo, 'http://') === 0) { // Check if it's from a supported platform $supported_domains = array( 'github.com', 'gitlab.com', 'bitbucket.org', 'codeberg.org' ); $parsed = parse_url($repo); if (!$parsed || !isset($parsed['host'])) { return false; } $domain_valid = false; foreach ($supported_domains as $domain) { if (strpos($parsed['host'], $domain) !== false) { $domain_valid = true; break; } } if (!$domain_valid) { return false; } // Extract path and validate format if (!isset($parsed['path'])) { return false; } $path = trim($parsed['path'], '/'); $parts = explode('/', $path); // Need at least owner/repo if (count($parts) < 2) { return false; } // Validate owner and repo names $owner = $parts[0]; $repo_name = $parts[1]; if (!preg_match('/^[a-zA-Z0-9\-_]+$/', $owner) || !preg_match('/^[a-zA-Z0-9\-_\.]+$/', $repo_name)) { return false; } return true; } // Check for platform prefix (e.g., gitlab:owner/repo) if (strpos($repo, ':') !== false) { list($platform, $repo_path) = explode(':', $repo, 2); // Validate platform $valid_platforms = array('github', 'gitlab', 'bitbucket', 'codeberg'); if (!in_array($platform, $valid_platforms)) { return false; } // Validate repo path if (!preg_match('/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/', $repo_path)) { return false; } return true; } // Standard format: owner/repo if (!preg_match('/^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/', $repo)) { return false; } // Check length limits $parts = explode('/', $repo); if (count($parts) !== 2) { return false; } // Owner: 1-39 characters (GitHub limit) if (strlen($parts[0]) < 1 || strlen($parts[0]) > 39) { return false; } // Repo name: 1-100 characters (GitHub limit) if (strlen($parts[1]) < 1 || strlen($parts[1]) > 100) { return false; } return true; } /** * Validate file path */ public static function validate_file_path($path) { // Remove leading/trailing slashes $path = trim($path, '/'); // Check for path traversal attempts $dangerous_patterns = array( '..', '//', '\\', '.git', '.env', 'wp-config', '.htaccess', '.htpasswd' ); foreach ($dangerous_patterns as $pattern) { if (stripos($path, $pattern) !== false) { return false; } } // Only allow safe characters if (!preg_match('/^[a-zA-Z0-9\-_\.\/]+$/', $path)) { return false; } // Check path depth (max 10 levels) if (substr_count($path, '/') > 10) { return false; } return true; } /** * Generate secure random token */ public static function generate_token($length = 32) { if (function_exists('random_bytes')) { return bin2hex(random_bytes($length)); } elseif (function_exists('openssl_random_pseudo_bytes')) { return bin2hex(openssl_random_pseudo_bytes($length)); } else { // Fallback to less secure method return wp_generate_password($length * 2, false, false); } } /** * Log security events */ public static function log_security_event($event_type, $details = array()) { $log_entry = array( 'timestamp' => current_time('mysql'), 'event_type' => $event_type, 'ip_address' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '', 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', 'details' => $details ); // Store in WordPress transient for review (expires in 7 days) $logs = get_transient('mcb_security_logs'); if (!is_array($logs)) { $logs = array(); } // Keep only last 100 entries if (count($logs) >= 100) { array_shift($logs); } $logs[] = $log_entry; set_transient('mcb_security_logs', $logs, 7 * DAY_IN_SECONDS); // For critical events, also log to error log if (in_array($event_type, array('invalid_nonce', 'rate_limit_exceeded', 'path_traversal_attempt'))) { error_log('GCV Security Event: ' . json_encode($log_entry)); } } } // Initialize security features MCB_Security::init();