added additional plugins
This commit is contained in:
parent
c85895d306
commit
00e60ec1b7
132 changed files with 27514 additions and 0 deletions
322
native/wordpress/maple-code-blocks/includes/class-security.php
Normal file
322
native/wordpress/maple-code-blocks/includes/class-security.php
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
<?php
|
||||
/**
|
||||
* Security Handler Class
|
||||
* Implements OWASP security best practices
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('WPINC')) {
|
||||
die('Direct access not permitted.');
|
||||
}
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class MCB_Security {
|
||||
|
||||
/**
|
||||
* Initialize security features
|
||||
*/
|
||||
public static function init() {
|
||||
// Add security headers
|
||||
add_action('send_headers', array(__CLASS__, 'add_security_headers'));
|
||||
|
||||
// Add Content Security Policy
|
||||
add_action('wp_head', array(__CLASS__, 'add_csp_meta'));
|
||||
|
||||
// Note: Global input sanitization removed - sanitize data at point of use instead
|
||||
// This prevents conflicts with other plugins (e.g., WPForms)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add security headers
|
||||
*/
|
||||
public static function add_security_headers() {
|
||||
// Only add headers on pages with our plugin
|
||||
if (!self::is_plugin_active_on_page()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// X-Content-Type-Options
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
// X-Frame-Options
|
||||
header('X-Frame-Options: SAMEORIGIN');
|
||||
|
||||
// X-XSS-Protection (legacy but still useful)
|
||||
header('X-XSS-Protection: 1; mode=block');
|
||||
|
||||
// Referrer Policy
|
||||
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Content Security Policy meta tag
|
||||
*/
|
||||
public static function add_csp_meta() {
|
||||
if (!self::is_plugin_active_on_page()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Strict CSP for code viewer areas
|
||||
$csp = "default-src 'self'; " .
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " . // Needed for Prism.js
|
||||
"style-src 'self' 'unsafe-inline'; " . // Needed for inline styles
|
||||
"img-src 'self' data: https:; " .
|
||||
"connect-src 'self'; " .
|
||||
"font-src 'self' data:; " .
|
||||
"object-src 'none'; " .
|
||||
"base-uri 'self'; " .
|
||||
"form-action 'self'; " .
|
||||
"frame-ancestors 'self';";
|
||||
|
||||
echo '<meta http-equiv="Content-Security-Policy" content="' . esc_attr($csp) . '">' . "\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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue