Changeset 3459867
- Timestamp:
- 02/12/2026 11:37:13 AM (3 weeks ago)
- Location:
- atomic-edge-security/trunk
- Files:
-
- 4 edited
-
atomicedge.php (modified) (2 diffs)
-
includes/class-atomicedge-api.php (modified) (1 diff)
-
includes/class-atomicedge-scanner.php (modified) (7 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
atomic-edge-security/trunk/atomicedge.php
r3459845 r3459867 4 4 * Plugin URI: https://atomicedge.io/wordpress 5 5 * Description: Connect your WordPress site to Atomic Edge WAF/CDN for advanced security protection, analytics, and access control management. 6 * Version: 2.2. 16 * Version: 2.2.2 7 7 * Requires at least: 5.8 8 8 * Requires PHP: 7.4 … … 26 26 27 27 // Plugin constants. 28 define( 'ATOMICEDGE_VERSION', '2.2. 1' );28 define( 'ATOMICEDGE_VERSION', '2.2.2' ); 29 29 define( 'ATOMICEDGE_PLUGIN_FILE', __FILE__ ); 30 30 define( 'ATOMICEDGE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -
atomic-edge-security/trunk/includes/class-atomicedge-api.php
r3454914 r3459867 803 803 return (bool) filter_var( $ip, FILTER_VALIDATE_IP ); 804 804 } 805 806 /** 807 * Get malware detection signatures from the Atomic Edge API. 808 * 809 * Signatures are cached locally to avoid repeated API calls. 810 * The cache is refreshed every 24 hours or when manually cleared. 811 * 812 * @param bool $force_refresh Force a refresh from the API. 813 * @return array|false Signature data or false on error. 814 */ 815 public function get_malware_signatures( $force_refresh = false ) { 816 $cache_key = 'atomicedge_malware_signatures'; 817 818 // Check for cached signatures unless force refresh. 819 if ( ! $force_refresh ) { 820 $cached = get_transient( $cache_key ); 821 if ( false !== $cached && is_array( $cached ) && ! empty( $cached['patterns'] ) ) { 822 return $cached; 823 } 824 } 825 826 // Fetch from API. 827 $response = $this->request( 'GET', '/wp/malware-signatures' ); 828 829 if ( ! $response['success'] || empty( $response['data'] ) ) { 830 // On API failure, try to use stale cache. 831 $stale = get_option( 'atomicedge_malware_signatures_backup' ); 832 if ( ! empty( $stale ) && is_array( $stale ) ) { 833 AtomicEdge::log( 'Using stale malware signature cache due to API failure' ); 834 return $stale; 835 } 836 837 AtomicEdge::log( 'Failed to fetch malware signatures', $response ); 838 return false; 839 } 840 841 $signatures = $response['data']; 842 843 // Cache for 24 hours. 844 $cache_duration = apply_filters( 'atomicedge_malware_signatures_cache_duration', DAY_IN_SECONDS ); 845 set_transient( $cache_key, $signatures, $cache_duration ); 846 847 // Also save as backup for API failures. 848 update_option( 'atomicedge_malware_signatures_backup', $signatures, false ); 849 850 AtomicEdge::log( 'Malware signatures updated', array( 'version' => $signatures['version'] ?? 'unknown' ) ); 851 852 return $signatures; 853 } 854 855 /** 856 * Clear malware signature cache. 857 * 858 * @return void 859 */ 860 public function clear_malware_signature_cache() { 861 delete_transient( 'atomicedge_malware_signatures' ); 862 } 805 863 } -
atomic-edge-security/trunk/includes/class-atomicedge-scanner.php
r3452356 r3459867 4 4 * 5 5 * File integrity checking and malware scanning functionality. 6 * Malware signatures are fetched from the Atomic Edge API to prevent 7 * the plugin from being flagged by hosting provider security scanners. 6 8 * 7 9 * @package AtomicEdge … … 40 42 */ 41 43 private const INTEGRITY_MANIFEST_REL_PATH = 'admin/assets/integrity/atomicedge-manifest.json'; 44 45 /** 46 * API instance for fetching signatures. 47 * 48 * @var AtomicEdge_API|null 49 */ 50 private $api = null; 42 51 43 52 /** … … 173 182 174 183 /** 175 * Quick rejection indicators - if none present, skip expensive regex. 176 * 177 * IMPORTANT: These must be UNCOMMON in legitimate code to be effective. 178 * Avoid common PHP functions like include(), require(), file_get_contents(). 179 * Focus on: 180 * - Obfuscation techniques (base64_decode+eval combos, hex encoding) 181 * - Known shell signatures 182 * - Malware-specific strings 183 * - Dangerous function+superglobal combos 184 * Quick rejection indicators fetched from API. 185 * These are fast string checks to skip expensive regex matching. 186 * 187 * @var array|null 188 */ 189 private $quick_rejection_indicators = null; 190 191 /** 192 * Signature data fetched from API. 193 * 194 * @var array|null 195 */ 196 private $api_signatures = null; 197 198 /** 199 * Diagnostics collected during the most recent scan. 184 200 * 185 201 * @var array 186 202 */ 187 private $quick_rejection_indicators = array( 188 // Code execution with dynamic input (the dangerous combo). 189 'eval(', 190 'assert(', 191 'create_function(', 192 // Shell execution functions. 193 'shell_exec(', 194 'passthru(', 195 'proc_open(', 196 'popen(', 197 'pcntl_exec(', 198 // Obfuscation patterns. 199 'base64_decode(', 200 'gzinflate(', 201 'gzuncompress(', 202 'str_rot13(', 203 'convert_uudecode(', 204 'edoced_46esab', // Reversed base64_decode. 205 // Hex-encoded strings (5+ consecutive \xNN patterns). 206 '\\x', 207 // Known webshell signatures. 208 'c99shell', 209 'r57shell', 210 'b374k', 211 'filesman', 212 'wso shell', 213 'weevely', 214 'phpspy', 215 // WordPress-specific malware. 216 'wp-vcd', 217 'tmpcontentx', 218 'wp_temp_setupx', 219 'derna.top', 220 '@noupdate', 221 'class.theme-modules', 222 'class.plugin-modules', 223 // Dangerous superglobal access patterns. 224 '$_get[', 225 '$_post[', 226 '$_request[', 227 '$_cookie[', 228 // Defacement/hack signatures. 229 'hacked by', 230 'owned by', 231 'backdoor', 232 'rootkit', 233 'webshell', 234 // System file access. 235 '/etc/passwd', 236 '/etc/shadow', 237 // Base64-encoded dangerous keywords. 238 'ZXZhbCg', // eval( 239 'YXNzZXJ0', // assert 240 'c3lzdGVt', // system 241 'c2hlbGxfZXhlYw', // shell_exec 242 ); 243 244 /** 245 * Diagnostics collected during the most recent scan. 203 private $scan_diagnostics = array(); 204 205 /** 206 * IDs of queue items to mark as done in batch at end of step. 246 207 * 247 208 * @var array 248 209 */ 249 private $scan_diagnostics = array();250 251 /**252 * IDs of queue items to mark as done in batch at end of step.253 *254 * @var array255 */256 210 private $pending_done_ids = array(); 257 211 258 212 /** 259 213 * Constructor. 260 */ 261 public function __construct() { 262 // Scanner doesn't need hooks - it's called on demand. 214 * 215 * @param AtomicEdge_API|null $api Optional API instance for fetching signatures. 216 */ 217 public function __construct( $api = null ) { 218 $this->api = $api; 219 } 220 221 /** 222 * Set the API instance. 223 * 224 * @param AtomicEdge_API $api API instance. 225 * @return void 226 */ 227 public function set_api( $api ) { 228 $this->api = $api; 263 229 } 264 230 … … 2993 2959 $content_lower = strtolower( $content ); 2994 2960 $has_indicator = false; 2995 foreach ( $this->quick_rejection_indicators as $indicator ) { 2961 $indicators = $this->get_quick_rejection_indicators(); 2962 foreach ( $indicators as $indicator ) { 2996 2963 if ( false !== strpos( $content_lower, $indicator ) ) { 2997 2964 $has_indicator = true; … … 3238 3205 * Get comprehensive malware detection patterns. 3239 3206 * 3240 * Patterns are organized by category for better reporting.3241 * Based on community research and known malware signatures.3207 * Patterns are fetched from the Atomic Edge API to prevent the plugin 3208 * from being flagged as malware by hosting provider security scanners. 3242 3209 * 3243 3210 * @return array Associative array of pattern groups. … … 3248 3215 } 3249 3216 3250 $this->patterns_cache = array( 3251 // Critical: Direct code execution patterns. 3252 'code_execution' => array( 3253 'eval\s*\(' => __( 'Eval function (code execution)', 'atomic-edge-security' ), 3254 'assert\s*\(' => __( 'Assert function (potential code execution)', 'atomic-edge-security' ), 3255 'create_function\s*\(' => __( 'Create function (dynamic code creation)', 'atomic-edge-security' ), 3256 'call_user_func\s*\(' => __( 'Call user func (dynamic function call)', 'atomic-edge-security' ), 3257 'call_user_func_array\s*\(' => __( 'Call user func array (dynamic function call)', 'atomic-edge-security' ), 3258 'preg_replace\s*\([^,]+["\'].*\/e["\']' => __( 'Preg replace with eval modifier', 'atomic-edge-security' ), 3259 'preg_replace_callback\s*\(' => __( 'Preg replace callback (potential code execution)', 'atomic-edge-security' ), 3260 ), 3261 3262 // Critical: Shell/system execution. 3263 'shell_execution' => array( 3264 '\bsystem\s*\(' => __( 'System command execution', 'atomic-edge-security' ), 3265 '\bexec\s*\(' => __( 'Exec command execution', 'atomic-edge-security' ), 3266 '\bshell_exec\s*\(' => __( 'Shell exec command', 'atomic-edge-security' ), 3267 '\bpassthru\s*\(' => __( 'Passthru command execution', 'atomic-edge-security' ), 3268 '\bpopen\s*\(' => __( 'Popen command execution', 'atomic-edge-security' ), 3269 '\bproc_open\s*\(' => __( 'Proc open command execution', 'atomic-edge-security' ), 3270 '\bpcntl_exec\s*\(' => __( 'PCNTL exec', 'atomic-edge-security' ), 3271 '`[^`]+`' => __( 'Backtick command execution', 'atomic-edge-security' ), 3272 ), 3273 3274 // Critical: Backdoor patterns. 3275 'backdoor_patterns' => array( 3276 '@eval\s*\(\s*\$_(?:GET|POST|REQUEST|COOKIE)' => __( 'Backdoor: eval with user input', 'atomic-edge-security' ), 3277 '@eval\s*\(\s*base64_decode' => __( 'Backdoor: eval with base64', 'atomic-edge-security' ), 3278 '\$_(?:GET|POST|REQUEST|COOKIE)\s*\[[^\]]+\]\s*\(' => __( 'Direct superglobal execution', 'atomic-edge-security' ), 3279 'extract\s*\(\s*\$_(?:GET|POST|REQUEST)' => __( 'Dangerous extract from user input', 'atomic-edge-security' ), 3280 'include\s*\(\s*\$_(?:GET|POST|REQUEST)' => __( 'Remote file inclusion via user input', 'atomic-edge-security' ), 3281 'require\s*\(\s*\$_(?:GET|POST|REQUEST)' => __( 'Remote file inclusion via user input', 'atomic-edge-security' ), 3282 'file_put_contents\s*\([^,]+,\s*\$_(?:GET|POST|REQUEST)' => __( 'File write from user input', 'atomic-edge-security' ), 3283 'fwrite\s*\([^,]+,\s*\$_(?:GET|POST|REQUEST)' => __( 'File write from user input', 'atomic-edge-security' ), 3284 ), 3285 3286 // High: Obfuscation techniques. 3287 'obfuscation' => array( 3288 'base64_decode\s*\(' => __( 'Base64 decoding (potential obfuscation)', 'atomic-edge-security' ), 3289 'gzinflate\s*\(' => __( 'Gzip inflate (potential obfuscation)', 'atomic-edge-security' ), 3290 'gzuncompress\s*\(' => __( 'Gzip uncompress (potential obfuscation)', 'atomic-edge-security' ), 3291 'str_rot13\s*\(' => __( 'ROT13 encoding (potential obfuscation)', 'atomic-edge-security' ), 3292 'convert_uudecode\s*\(' => __( 'UUdecode (potential obfuscation)', 'atomic-edge-security' ), 3293 '\\\\x[0-9a-fA-F]{2}(\\\\x[0-9a-fA-F]{2}){5,}' => __( 'Hex encoded strings', 'atomic-edge-security' ), 3294 'chr\s*\(\s*\d+\s*\)\s*\.\s*chr\s*\(\s*\d+\s*\)(\s*\.\s*chr\s*\(\s*\d+\s*\)){3,}' => __( 'Chr() string building', 'atomic-edge-security' ), 3295 'edoced_46esab' => __( 'Reversed base64_decode', 'atomic-edge-security' ), 3296 'strrev\s*\(["\'][^"\']+["\']\)' => __( 'String reversal (obfuscation)', 'atomic-edge-security' ), 3297 ), 3298 3299 // High: Known webshell signatures. 3300 'webshells' => array( 3301 'c99shell' => __( 'C99 shell signature', 'atomic-edge-security' ), 3302 'r57shell' => __( 'R57 shell signature', 'atomic-edge-security' ), 3303 'b374k' => __( 'B374K shell signature', 'atomic-edge-security' ), 3304 'FilesMan' => __( 'FilesMan shell signature', 'atomic-edge-security' ), 3305 'WSO\s+[\d\.]+' => __( 'WSO shell signature', 'atomic-edge-security' ), 3306 'Weevely' => __( 'Weevely shell signature', 'atomic-edge-security' ), 3307 'php\s*spy' => __( 'PHPSpy shell signature', 'atomic-edge-security' ), 3308 'PHANTOMJS' => __( 'PhantomJS shell signature', 'atomic-edge-security' ), 3309 'Mister Spy' => __( 'Mister Spy shell signature', 'atomic-edge-security' ), 3310 'Afghan Shell' => __( 'Afghan Shell signature', 'atomic-edge-security' ), 3311 'Shell by' => __( 'Generic shell signature', 'atomic-edge-security' ), 3312 'SHELL_PASSWORD' => __( 'Shell password variable', 'atomic-edge-security' ), 3313 '0day' => __( '0day exploit reference', 'atomic-edge-security' ), 3314 ), 3315 3316 // High: WordPress-specific malware. 3317 'wordpress_malware' => array( 3318 'wp-vcd' => __( 'WP-VCD malware', 'atomic-edge-security' ), 3319 'class\.theme-modules\.php' => __( 'WP-VCD theme modules', 'atomic-edge-security' ), 3320 'class\.plugin-modules\.php' => __( 'WP-VCD plugin modules', 'atomic-edge-security' ), 3321 'wp-tmp\.php' => __( 'WP-VCD temp file', 'atomic-edge-security' ), 3322 'tmpcontentx' => __( 'WP-VCD content injection', 'atomic-edge-security' ), 3323 'wp_temp_setupx' => __( 'WP-VCD setup function', 'atomic-edge-security' ), 3324 'derna\.top' => __( 'Known malware domain', 'atomic-edge-security' ), 3325 '/\*\s*@noupdate\s*\*/' => __( 'Plugin update suppression', 'atomic-edge-security' ), 3326 ), 3327 3328 // Medium: Suspicious file operations. 3329 'file_operations' => array( 3330 'file_get_contents\s*\(["\']https?://' => __( 'Remote file fetch', 'atomic-edge-security' ), 3331 'file_get_contents\s*\(["\']php://input' => __( 'Raw POST data read', 'atomic-edge-security' ), 3332 'curl_exec\s*\(' => __( 'cURL execution', 'atomic-edge-security' ), 3333 'fsockopen\s*\(' => __( 'Socket connection', 'atomic-edge-security' ), 3334 'stream_socket_client\s*\(' => __( 'Stream socket client', 'atomic-edge-security' ), 3335 ), 3336 3337 // Medium: Base64 encoded suspicious keywords. 3338 'base64_keywords' => array( 3339 'ZXZhbCg' => __( 'Base64: eval(', 'atomic-edge-security' ), 3340 'YXNzZXJ0' => __( 'Base64: assert', 'atomic-edge-security' ), 3341 'c3lzdGVt' => __( 'Base64: system', 'atomic-edge-security' ), 3342 'ZXhlYyg' => __( 'Base64: exec(', 'atomic-edge-security' ), 3343 'c2hlbGxfZXhlYw' => __( 'Base64: shell_exec', 'atomic-edge-security' ), 3344 'cGFzc3RocnU' => __( 'Base64: passthru', 'atomic-edge-security' ), 3345 'JF9HRV' => __( 'Base64: $_GET', 'atomic-edge-security' ), 3346 'JF9QT1NU' => __( 'Base64: $_POST', 'atomic-edge-security' ), 3347 'JF9SRVFVRVNU' => __( 'Base64: $_REQUEST', 'atomic-edge-security' ), 3348 'JF9DT09LSUU' => __( 'Base64: $_COOKIE', 'atomic-edge-security' ), 3349 'SFRUUF9VU0VSX0FHRU5U' => __( 'Base64: HTTP_USER_AGENT', 'atomic-edge-security' ), 3350 'R0xPQkFMU' => __( 'Base64: GLOBALS', 'atomic-edge-security' ), 3351 ), 3352 3353 // Medium: Suspicious strings and indicators. 3354 'suspicious_strings' => array( 3355 '/etc/passwd' => __( 'Password file access', 'atomic-edge-security' ), 3356 '/etc/shadow' => __( 'Shadow file access', 'atomic-edge-security' ), 3357 'HACKED BY' => __( 'Defacement signature', 'atomic-edge-security' ), 3358 'owned by' => __( 'Defacement signature', 'atomic-edge-security' ), 3359 'backdoor' => __( 'Backdoor keyword', 'atomic-edge-security' ), 3360 'rootkit' => __( 'Rootkit keyword', 'atomic-edge-security' ), 3361 'c999sh' => __( 'Shell variant', 'atomic-edge-security' ), 3362 'r57sh' => __( 'Shell variant', 'atomic-edge-security' ), 3363 'webshell' => __( 'Webshell keyword', 'atomic-edge-security' ), 3364 'cmd\.exe' => __( 'Windows command execution', 'atomic-edge-security' ), 3365 'powershell\.exe' => __( 'PowerShell execution', 'atomic-edge-security' ), 3366 'uname\s+-a' => __( 'System information gathering', 'atomic-edge-security' ), 3367 'whoami' => __( 'User identification command', 'atomic-edge-security' ), 3368 ), 3369 3370 // Medium: Network indicators. 3371 'network_indicators' => array( 3372 'fsockopen\s*\(["\']udp://' => __( 'UDP socket (potential DDoS)', 'atomic-edge-security' ), 3373 'socket_create\s*\(\s*AF_INET' => __( 'Raw socket creation', 'atomic-edge-security' ), 3374 'curl_setopt[^;]+CURLOPT_FOLLOWLOCATION' => __( 'cURL with redirect following', 'atomic-edge-security' ), 3375 ), 3376 3377 // Low: Potentially dangerous functions (context-dependent). 3378 'potentially_dangerous' => array( 3379 'unserialize\s*\(\s*\$_(?:GET|POST|REQUEST|COOKIE)' => __( 'Unserialize user input (object injection)', 'atomic-edge-security' ), 3380 'serialize\s*\([^)]+\)\s*\.' => __( 'Serialized data concatenation', 'atomic-edge-security' ), 3381 'ini_set\s*\(["\'](?:allow_url_fopen|allow_url_include)' => __( 'INI override for remote includes', 'atomic-edge-security' ), 3382 'ini_set\s*\(["\']disable_functions' => __( 'Attempt to modify disabled functions', 'atomic-edge-security' ), 3383 'error_reporting\s*\(\s*0\s*\)' => __( 'Error reporting disabled', 'atomic-edge-security' ), 3384 'set_error_handler\s*\(\s*null' => __( 'Error handler nullified', 'atomic-edge-security' ), 3385 ), 3386 ); 3387 3217 // Try to fetch from API. 3218 $api_patterns = $this->get_api_signatures(); 3219 3220 if ( ! empty( $api_patterns['patterns'] ) ) { 3221 $this->patterns_cache = $api_patterns['patterns']; 3222 return $this->patterns_cache; 3223 } 3224 3225 // Fallback: Return empty patterns if API unavailable. 3226 // This ensures the scanner doesn't crash, but it will not detect malware. 3227 AtomicEdge::log( 'Scanner running without malware signatures - API unavailable' ); 3228 $this->patterns_cache = array(); 3388 3229 return $this->patterns_cache; 3230 } 3231 3232 /** 3233 * Get quick rejection indicators. 3234 * 3235 * These are fast string checks to skip expensive regex matching. 3236 * 3237 * @return array List of quick rejection strings. 3238 */ 3239 private function get_quick_rejection_indicators() { 3240 if ( is_array( $this->quick_rejection_indicators ) ) { 3241 return $this->quick_rejection_indicators; 3242 } 3243 3244 $api_signatures = $this->get_api_signatures(); 3245 3246 if ( ! empty( $api_signatures['quick_indicators'] ) ) { 3247 $this->quick_rejection_indicators = $api_signatures['quick_indicators']; 3248 return $this->quick_rejection_indicators; 3249 } 3250 3251 // Fallback: Empty array. 3252 $this->quick_rejection_indicators = array(); 3253 return $this->quick_rejection_indicators; 3254 } 3255 3256 /** 3257 * Get API signatures (cached). 3258 * 3259 * @return array Signature data from API. 3260 */ 3261 private function get_api_signatures() { 3262 if ( is_array( $this->api_signatures ) ) { 3263 return $this->api_signatures; 3264 } 3265 3266 // Try to get API instance. 3267 $api = $this->api; 3268 if ( null === $api ) { 3269 // Try to get global API instance. 3270 if ( class_exists( 'AtomicEdge' ) && method_exists( 'AtomicEdge', 'get_instance' ) ) { 3271 $instance = AtomicEdge::get_instance(); 3272 if ( method_exists( $instance, 'get_api' ) ) { 3273 $api = $instance->get_api(); 3274 } 3275 } 3276 } 3277 3278 if ( null === $api || ! method_exists( $api, 'get_malware_signatures' ) ) { 3279 $this->api_signatures = array(); 3280 return $this->api_signatures; 3281 } 3282 3283 $signatures = $api->get_malware_signatures(); 3284 3285 if ( false === $signatures || ! is_array( $signatures ) ) { 3286 $this->api_signatures = array(); 3287 return $this->api_signatures; 3288 } 3289 3290 $this->api_signatures = $signatures; 3291 return $this->api_signatures; 3292 } 3293 3294 /** 3295 * Check if the scanner has signatures loaded. 3296 * 3297 * @return bool True if signatures are available. 3298 */ 3299 public function has_signatures() { 3300 $signatures = $this->get_api_signatures(); 3301 return ! empty( $signatures['patterns'] ); 3302 } 3303 3304 /** 3305 * Get signature version. 3306 * 3307 * @return string|null Version string or null if not available. 3308 */ 3309 public function get_signature_version() { 3310 $signatures = $this->get_api_signatures(); 3311 return isset( $signatures['version'] ) ? $signatures['version'] : null; 3389 3312 } 3390 3313 … … 3396 3319 */ 3397 3320 private function get_pattern_severity( $category ) { 3398 $severity_map = array( 3399 'code_execution' => 'critical', 3400 'shell_execution' => 'critical', 3401 'backdoor_patterns' => 'critical', 3402 'webshells' => 'critical', 3403 'wordpress_malware' => 'critical', 3404 'obfuscation' => 'high', 3405 'file_operations' => 'medium', 3406 'base64_keywords' => 'medium', 3407 'suspicious_strings' => 'medium', 3408 'network_indicators' => 'medium', 3409 'potentially_dangerous' => 'low', 3410 ); 3411 3412 return isset( $severity_map[ $category ] ) ? $severity_map[ $category ] : 'medium'; 3321 // Try to get from API signatures. 3322 $signatures = $this->get_api_signatures(); 3323 3324 if ( ! empty( $signatures['severity_map'][ $category ] ) ) { 3325 return $signatures['severity_map'][ $category ]; 3326 } 3327 3328 // Default fallback. 3329 return 'medium'; 3413 3330 } 3414 3331 -
atomic-edge-security/trunk/readme.txt
r3459845 r3459867 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 2.2. 17 Stable tag: 2.2.2 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 110 110 111 111 == Changelog == 112 113 = 2.2.2 = 114 * FIX: Malware scanner signatures moved to remote API to prevent hosting providers from flagging the plugin as malware 115 * Signatures are now fetched from the Atomic Edge API and cached locally for 24 hours 116 * This resolves false positives from security scanners detecting plaintext malware patterns in the plugin source code 112 117 113 118 = 2.2.1 =
Note: See TracChangeset
for help on using the changeset viewer.