Plugin Directory

Changeset 3461467


Ignore:
Timestamp:
02/14/2026 06:31:38 PM (6 weeks ago)
Author:
lkoudal
Message:
  • 2026-02-13
  • IMPROVED: Litespeed servers - Added documentation and in-app notices for all security headers (CSP, X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security, Referrer-Policy, Permissions-Policy). LiteSpeed users can add headers directly to .htaccess using the examples in each test description. Thank you Tom for the feedback.
  • FIX: Events Logger, Overview, and Visitor Log – Country flags now correctly show the event/visitor IP's country instead of the logged-in admin's IP when the site is behind Cloudflare or similar proxies.
  • Improved: Core Scanner - Interface loads faster with tabs lazy-loading content in different tabs.
  • IMPROVED: Firewall – When "Block IP Network" is enabled, known social and link-preview crawlers (e.g. Facebook, LinkedIn, Twitter) are no longer blocked by default. Link previews when you share your site on social networks now work without having to whitelist IPs.
Location:
security-ninja
Files:
772 added
4 deleted
18 edited

Legend:

Unmodified
Added
Removed
  • security-ninja/trunk/css/min/sn-style.css

    r3446591 r3461467  
    3333  background-color: #047857;
    3434  color: #ffffff;
     35  margin-right: 10px;
    3536  border: 1px solid #047857;
    3637}
     
    13861387  table-layout: fixed;
    13871388  max-width: 1370px;
    1388   margin: 0 auto;
    13891389}
    13901390
  • security-ninja/trunk/css/sn-style.scss

    r3446591 r3461467  
    3636    color: #ffffff;
    3737    border: 1px solid #047857;
     38    margin-right: 10px;
    3839}
    3940
  • security-ninja/trunk/includes/sidebar.php

    r3451093 r3461467  
    243243    ?>
    244244        <div class="sidebarsection feature upgradepro">
    245             <h3>Effortless Security for Your Site!</h3>
     245            <h3><?php
     246    esc_html_e( 'Effortless Security for Your Site!', 'security-ninja' );
     247    ?></h3>
    246248            <ul>
    247                 <li><strong>Easy Setup:</strong> Install in minutes, no technical skills required.</li>
    248                 <li><strong>Automatic Protection:</strong> Real-time firewall and automated scans.</li>
    249                 <li><strong>Spam &amp; Bot Blocking:</strong> Keeps your site clean and visitors safe.</li>
    250                 <li><strong>Secure Logins:</strong> Protect your login page from attacks.</li>
     249                <li><strong><?php
     250    esc_html_e( 'Get set up in minutes', 'security-ninja' );
     251    ?></strong> &mdash; <?php
     252    esc_html_e( 'Guided wizard, no technical skills needed.', 'security-ninja' );
     253    ?></li>
     254                <li><strong><?php
     255    esc_html_e( 'Stay protected 24/7', 'security-ninja' );
     256    ?></strong> &mdash; <?php
     257    esc_html_e( 'Real-time firewall and automatic scans.', 'security-ninja' );
     258    ?></li>
     259                <li><strong><?php
     260    esc_html_e( 'Keep spam and bots out', 'security-ninja' );
     261    ?></strong> &mdash; <?php
     262    esc_html_e( 'Cleaner site, safer visitors.', 'security-ninja' );
     263    ?></li>
     264                <li><strong><?php
     265    esc_html_e( 'Lock down logins', 'security-ninja' );
     266    ?></strong> &mdash; <?php
     267    esc_html_e( 'Block brute force; add 2FA and hidden login URL.', 'security-ninja' );
     268    ?></li>
     269                <li><strong><?php
     270    esc_html_e( 'Find and fix issues fast', 'security-ninja' );
     271    ?></strong> &mdash; <?php
     272    esc_html_e( 'Malware &amp; core scans, one-click fixes.', 'security-ninja' );
     273    ?></li>
     274                <li><strong><?php
     275    esc_html_e( 'See who did what', 'security-ninja' );
     276    ?></strong> &mdash; <?php
     277    esc_html_e( 'Simple audit log of changes.', 'security-ninja' );
     278    ?></li>
     279                <li><strong><?php
     280    esc_html_e( 'Run scans on your schedule', 'security-ninja' );
     281    ?></strong> &mdash; <?php
     282    esc_html_e( 'Set it and forget it.', 'security-ninja' );
     283    ?></li>
     284                <li><strong><?php
     285    esc_html_e( 'Try risk-free', 'security-ninja' );
     286    ?></strong> &mdash; <?php
     287    esc_html_e( '30-day money-back guarantee.', 'security-ninja' );
     288    ?></li>
    251289            </ul>
    252             <p>
    253                 <em>Upgrade to Pro and get effortless security today!</em>
    254             </p>
    255 
    256290            <p style="margin-top: 10px;text-align: center;">
    257                 <a href="https://wpsecurityninja.com/pricing/?utm_source=overview-tab&amp;utm_medium=plugin&amp;utm_content=explore-pro&amp;utm_campaign=security_ninja_v5.235" class="" target="_blank">Get started now!</a><br><small>30-days money back guarantee</small>
     291                <a href="https://wpsecurityninja.com/pricing/?utm_source=overview-tab&amp;utm_medium=plugin&amp;utm_content=explore-pro&amp;utm_campaign=security_ninja_v5.235" class="" target="_blank" rel="noopener"><?php
     292    esc_html_e( 'Read more', 'security-ninja' );
     293    ?></a>
    258294            </p>
    259295        </div>
  • security-ninja/trunk/languages/security-ninja.pot

    r3458434 r3461467  
    20062006msgstr ""
    20072007
     2008#: modules/auto-fixer/class-wf-sn-fixes.php:873
     2009msgid "LiteSpeed detected:"
     2010msgstr ""
     2011
     2012#: modules/auto-fixer/class-wf-sn-fixes.php:873
     2013msgid "The PHP header interface may not work on LiteSpeed servers. If security headers do not appear in your response, add them directly to your .htaccess file (see the test descriptions for each header for examples)."
     2014msgstr ""
     2015
    20082016#: modules/auto-fixer/class-wf-sn-fixes.php:1040
    20092017msgid ""
     
    77587766msgstr ""
    77597767
     7768#: sn-tests-description.php:48 sn-tests-description.php:73 sn-tests-description.php:109 sn-tests-description.php:144
     7769#: sn-tests-description.php:170 sn-tests-description.php:202
     7770msgid "LiteSpeed servers:"
     7771msgstr ""
     7772
     7773#: sn-tests-description.php:73
     7774msgid "The PHP header interface may not work correctly on LiteSpeed. If CSP headers are not appearing in your response, add the policy directly to your .htaccess file using the example below."
     7775msgstr ""
     7776
     7777#: sn-tests-description.php:48 sn-tests-description.php:109 sn-tests-description.php:144 sn-tests-description.php:170
     7778#: sn-tests-description.php:202
     7779msgid "The PHP header interface may not work correctly on LiteSpeed. If this header is not appearing in your response, add it directly to your .htaccess file using the example below."
     7780msgstr ""
     7781
    77607782#: sn-tests-description.php:86
    77617783msgid "Read our guide to configuring Content Security Policy"
  • security-ninja/trunk/modules/cloud-firewall/class-sn-geolocation.php

    r3446591 r3461467  
    158158     */
    159159    public static function geolocate_ip( $ip_address = '', $fallback = false, $api_fallback = true ) {
    160         // If GEOIP is enabled in CloudFlare, we can use that (Settings -> CloudFlare Settings -> Settings Overview).
    161         if ( !empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) {
    162             // WPCS: input var ok, CSRF ok.
    163             $country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) );
    164             // WPCS: input var ok, CSRF ok.
    165         } elseif ( !empty( $_SERVER['GEOIP_COUNTRY_CODE'] ) ) {
    166             // WPCS: input var ok, CSRF ok.
    167             // WP.com VIP has a variable available.
    168             $country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['GEOIP_COUNTRY_CODE'] ) ) );
    169             // WPCS: input var ok, CSRF ok.
    170         } elseif ( !empty( $_SERVER['HTTP_X_COUNTRY_CODE'] ) ) {
    171             // WPCS: input var ok, CSRF ok.
    172             // VIP Go has a variable available also.
    173             $country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_COUNTRY_CODE'] ) ) );
    174             // WPCS: input var ok, CSRF ok.
    175         }
    176         if ( isset( $_SERVER["HTTP_CF_CONNECTING_IP"] ) ) {
     160        $country_code = '';
     161        // Only use request headers when geolocating the current request's IP (no explicit IP passed).
     162        if ( empty( $ip_address ) ) {
     163            // If GEOIP is enabled in CloudFlare, we can use that (Settings -> CloudFlare Settings -> Settings Overview).
     164            if ( !empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) {
     165                // WPCS: input var ok, CSRF ok.
     166                $country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) );
     167                // WPCS: input var ok, CSRF ok.
     168            } elseif ( !empty( $_SERVER['GEOIP_COUNTRY_CODE'] ) ) {
     169                // WPCS: input var ok, CSRF ok.
     170                // WP.com VIP has a variable available.
     171                $country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['GEOIP_COUNTRY_CODE'] ) ) );
     172                // WPCS: input var ok, CSRF ok.
     173            } elseif ( !empty( $_SERVER['HTTP_X_COUNTRY_CODE'] ) ) {
     174                // WPCS: input var ok, CSRF ok.
     175                // VIP Go has a variable available also.
     176                $country_code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_COUNTRY_CODE'] ) ) );
     177                // WPCS: input var ok, CSRF ok.
     178            }
     179        }
     180        // Only use current request's IP from Cloudflare when we weren't given a specific IP to look up.
     181        if ( empty( $ip_address ) && isset( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
    177182            $ip_address = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) );
    178183        }
  • security-ninja/trunk/modules/cloud-firewall/cloud-firewall.php

    r3458434 r3461467  
    288288
    289289    /**
    290      * endsWith. - ref https://www.php.net/manual/en/function.str-ends-with.php
    291      *
    292      * @author  javalc6 at gmail dot com
    293      * @since   v0.0.1
    294      * @version v1.0.0  Monday, August 30th, 2021.
    295      * @param   mixed   $haystack
    296      * @param   mixed   $needle
    297      * @return  mixed
    298      */
    299     private static function string_ends_with( $haystack, $needle ) {
    300         $length = strlen( $needle );
    301         return ( $length > 0 ? substr( $haystack, -$length ) === $needle : true );
    302     }
    303 
    304     /**
    305290     * Validate a crawlers IP against the hostname
    306291     *
     
    352337        );
    353338        foreach ( $valid_host_names as $valid_host ) {
    354             if ( self::string_ends_with( $hostname, $valid_host ) ) {
     339            if ( Wf_sn_cf_Utils::string_ends_with( $hostname, $valid_host ) ) {
    355340                $returned_ip = gethostbyname( $hostname );
    356341                if ( $returned_ip === $testip ) {
     
    508493            if ( strpos( $whitelist_item, '/' ) !== false ) {
    509494                // Use the proper CIDR matching function
    510                 if ( self::ipCIDRMatch( $current_user_ip, $whitelist_item ) ) {
     495                if ( Wf_sn_cf_Utils::ipCIDRMatch( $current_user_ip, $whitelist_item ) ) {
    511496                    return true;
    512497                    // IP is whitelisted
     
    11251110
    11261111    /**
    1127      * Update local list of blocked IPs.
    1128      * First delete expired > 24 hours.
    1129      * Then download and bulk add entries
    1130      *
    1131      * @author  Lars Koudal
    1132      * @since   v0.0.1
    1133      * @version v1.0.0  Sunday, May 30th, 2021.
    1134      * @version v1.0.1  Wednesday, June 9th, 2021.
    1135      * @access  public static
    1136      * @global
    1137      * @param   boolean $force  Default: false
    1138      * @return  void
    1139      */
    1140     public static function action_update_blocked_ips( $force = false ) {
    1141         $listips = self::get_network_listips();
    1142         // @todo - right place for it?
    1143         if ( !$listips ) {
    1144             wf_sn_el_modules::log_event( 'security_ninja', 'update_blocked_ips', 'Error getting blocked IPs from server' );
    1145             return false;
    1146         }
    1147         global $wpdb;
    1148         // Cleaning up
    1149         $table_name = $wpdb->prefix . 'wf_sn_cf_bl_ips';
    1150         $delquery = "DELETE FROM `{$table_name}` WHERE HOUR(TIMEDIFF(NOW(), tid))>24;";
    1151         $delres = $wpdb->query( $delquery );
    1152         if ( $delres ) {
    1153             wf_sn_el_modules::log_event(
    1154                 'security_ninja',
    1155                 'update_blocked_ips',
    1156                 sprintf( esc_html__( 'Removed %1$s IPs from the Blocklist - older than 24 hours.', 'security-ninja' ), intval( $delres ) ),
    1157                 ''
    1158             );
    1159         } else {
    1160             wf_sn_el_modules::log_event( 'security_ninja', 'update_blocked_ips', 'No old IPs needs to be removed.' );
    1161         }
    1162         $blockedips = json_decode( $listips, true );
    1163         if ( $blockedips && is_array( $blockedips ) && isset( $blockedips['ips'] ) && is_array( $blockedips['ips'] ) ) {
    1164             global $wpdb;
    1165             $current_count = 0;
    1166             $limit = 15;
    1167             $longquery = '';
    1168             $totalcount = 0;
    1169             $timenow = current_time( 'mysql' );
    1170             foreach ( $blockedips['ips'] as $ip ) {
    1171                 if ( 0 === $current_count ) {
    1172                     $longquery .= ' INSERT IGNORE INTO `' . $table_name . "` (`ip`) VALUES ('" . esc_sql( $ip ) . "')";
    1173                 } else {
    1174                     $longquery .= ",('" . esc_sql( $ip ) . "')";
    1175                 }
    1176                 $current_count++;
    1177                 if ( $current_count > $limit ) {
    1178                     $longquery .= ';';
    1179                     // add ending semicolon before executing
    1180                     $wpdb->query( $longquery );
    1181                     $longquery = '';
    1182                     $current_count = 0;
    1183                 }
    1184                 $totalcount++;
    1185             }
    1186             // Leftovers?
    1187             if ( $current_count > 0 ) {
    1188                 $longquery .= ';';
    1189                 // add ending semicolon before executing
    1190                 $wpdb->query( $longquery );
    1191                 $longquery = '';
    1192                 $current_count = 0;
    1193             }
    1194         }
    1195     }
    1196 
    1197     /**
    11981112     * Prune events log table
    11991113     *
     
    12071121    public static function prune_visitor_log( $force = false ) {
    12081122        global $wpdb;
    1209         $trackvisits_howlong = intval( self::$options['trackvisits_howlong'] );
     1123        $trackvisits_howlong = absint( self::$options['trackvisits_howlong'] );
    12101124        if ( !$trackvisits_howlong ) {
    12111125            $trackvisits_howlong = 2;
     
    12131127        }
    12141128        $table_name = $wpdb->prefix . 'wf_sn_cf_vl';
    1215         if ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) ) === $table_name ) {
    1216             $wpdb->query( 'DELETE FROM ' . $wpdb->prefix . 'wf_sn_cf_vl' . " WHERE timestamp < DATE_SUB(NOW(), INTERVAL {$trackvisits_howlong} DAY);" );
     1129        if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
     1130            $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'wf_sn_cf_vl WHERE timestamp < DATE_SUB(NOW(), INTERVAL %d DAY)', $trackvisits_howlong ) );
    12171131            $max_records = 5000;
    12181132            // Sane limit for visitor log entries
     
    12961210     */
    12971211    public static function schedule_cron_jobs() {
    1298         // Update GEOIP database - once a month
    1299         if ( !wp_next_scheduled( 'secnin_update_geoip' ) ) {
    1300             wp_schedule_event( time() + 30, 'weekly', 'secnin_update_geoip' );
    1301         }
    1302         // Update cloud IPs
    1303         if ( !wp_next_scheduled( 'secnin_update_cloud_firewall' ) ) {
    1304             wp_schedule_event( time() + 15, 'twicedaily', 'secnin_update_cloud_firewall' );
    1305         }
    1306         // Prune local banned IPs (hourly; migrate existing twicedaily to hourly)
    1307         $next_prune = wp_next_scheduled( 'secnin_prune_banned' );
    1308         if ( $next_prune ) {
    1309             wp_unschedule_event( $next_prune, 'secnin_prune_banned' );
    1310         }
    1311         wp_schedule_event( time() + 3600, 'hourly', 'secnin_prune_banned' );
    1312         // Prune visitor log
    1313         if ( !wp_next_scheduled( 'secnin_prune_visitor_log' ) ) {
    1314             wp_schedule_event( time() + 3600, 'twicedaily', 'secnin_prune_visitor_log' );
    1315         }
    1316         // Update blocked IPs from central server
    1317         if ( !wp_next_scheduled( 'secnin_update_blocked_ips' ) ) {
    1318             wp_schedule_event( time() + 45, 'twicedaily', 'secnin_update_blocked_ips' );
    1319         }
    13201212    }
    13211213
     
    22732165
    22742166    /**
    2275      * ipCIDRMatch.
    2276      *
    2277      * @author  Unknown
    2278      * @author  Lars Koudal
    2279      * @since   v0.0.1
    2280      * @version v1.0.0   Saturday, August 20th, 2022.   
    2281      * @version v1.0.1   Tuesday, August 27th, 2024.
    2282      * @access  public static
    2283      * @param   string $ip   The IP address to check.
    2284      * @param   string $cidr The CIDR range to check against.
    2285      * @return  bool         True if the IP matches the CIDR range, false otherwise.
    2286      */
    2287     public static function ipCIDRMatch( $ip, $cidr ) {
    2288         $c = explode( '/', $cidr );
    2289         $subnet = ( isset( $c[0] ) ? $c[0] : NULL );
    2290         $mask = ( isset( $c[1] ) ? (int) $c[1] : NULL );
    2291         if ( filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
    2292             $ipVersion = 'v4';
    2293         } elseif ( filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
    2294             $ipVersion = 'v6';
    2295         } else {
    2296             return false;
    2297         }
    2298         switch ( $ipVersion ) {
    2299             case 'v4':
    2300                 if ( $mask === NULL || $mask < 0 || $mask > 32 ) {
    2301                     return false;
    2302                 }
    2303                 return self::IPv4Match( $ip, $subnet, $mask );
    2304             case 'v6':
    2305                 if ( $mask === NULL || $mask < 0 || $mask > 128 ) {
    2306                     return false;
    2307                 }
    2308                 return self::IPv6Match( $ip, $subnet, $mask );
    2309             default:
    2310                 return false;
    2311         }
    2312     }
    2313 
    2314     /**
    2315      * inspired by: http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet
    2316      *
    2317      * @author  Unknown
    2318      * @since   v0.0.1
    2319      * @version v1.0.0  Tuesday, May 14th, 2024.
    2320      * @access  private static
    2321      * @param   mixed   $subnetMask
    2322      * @return  mixed
    2323      */
    2324     private static function IPv6MaskToByteArray( $subnetMask ) {
    2325         $addr = str_repeat( "f", $subnetMask / 4 );
    2326         switch ( $subnetMask % 4 ) {
    2327             case 0:
    2328                 break;
    2329             case 1:
    2330                 $addr .= "8";
    2331                 break;
    2332             case 2:
    2333                 $addr .= "c";
    2334                 break;
    2335             case 3:
    2336                 $addr .= "e";
    2337                 break;
    2338         }
    2339         $addr = str_pad( $addr, 32, '0' );
    2340         $addr = pack( "H*", $addr );
    2341         return $addr;
    2342     }
    2343 
    2344     /**
    2345      * inspired by: http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet
    2346      *
    2347      * @author  Unknown
    2348      * @author  Lars Koudal
    2349      * @since   v0.0.1
    2350      * @version v1.0.0  Tuesday, May 14th, 2024.   
    2351      * @version v1.0.1  Tuesday, August 27th, 2024.
    2352      * @access  private static
    2353      * @param   mixed   $address       
    2354      * @param   mixed   $subnetAddress 
    2355      * @param   mixed   $subnetMask     
    2356      * @return  mixed
    2357      */
    2358     private static function IPv6Match( $address, $subnetAddress, $subnetMask ) {
    2359         // Validate the subnet address
    2360         if ( !filter_var( $subnetAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) || $subnetMask === NULL || $subnetMask === "" || $subnetMask < 0 || $subnetMask > 128 ) {
    2361             return false;
    2362         }
    2363         // Convert addresses to binary form
    2364         $subnet = inet_pton( $subnetAddress );
    2365         $addr = inet_pton( $address );
    2366         // Ensure that both addresses were converted correctly
    2367         if ( $subnet === false || $addr === false ) {
    2368             return false;
    2369         }
    2370         // Convert the subnet mask to a binary string
    2371         $binMask = self::IPv6MaskToByteArray( $subnetMask );
    2372         // Perform the bitwise AND operation and compare
    2373         return ($addr & $binMask) === $subnet;
    2374     }
    2375 
    2376     /**
    2377      * inspired by: http://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php5
    2378      *
    2379      * @author  Unknown
    2380      * @since   v0.0.1
    2381      * @version v1.0.0  Tuesday, May 14th, 2024.
    2382      * @access  private static
    2383      * @param   mixed   $address       
    2384      * @param   mixed   $subnetAddress 
    2385      * @param   mixed   $subnetMask     
    2386      * @return  mixed
    2387      */
    2388     private static function IPv4Match( $address, $subnetAddress, $subnetMask ) {
    2389         if ( !filter_var( $subnetAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) || $subnetMask === NULL || $subnetMask === "" || $subnetMask < 0 || $subnetMask > 32 ) {
    2390             return false;
    2391         }
    2392         $address = ip2long( $address );
    2393         $subnetAddress = ip2long( $subnetAddress );
    2394         $mask = -1 << 32 - $subnetMask;
    2395         $subnetAddress &= $mask;
    2396         # nb: in case the supplied subnet wasn't correctly aligned
    2397         return ($address & $mask) == $subnetAddress;
    2398     }
    2399 
    2400     /**
    24012167     * Checks if an IP is in array
    24022168     *
     
    24652231        }
    24662232        // Use proper CIDR-aware whitelist checking
    2467         if ( self::is_whitelisted( $current_user_ip, $local_whitelist ) ) {
     2233        if ( Wf_sn_cf_Utils::is_whitelisted( $current_user_ip, $local_whitelist ) ) {
    24682234            return false;
    24692235        }
     
    24752241                    return 'IP is in local blacklist.';
    24762242                }
    2477                 if ( self::ipCIDRMatch( $ip, $bl ) ) {
     2243                if ( Wf_sn_cf_Utils::ipCIDRMatch( $ip, $bl ) ) {
    24782244                    return 'IP is in local blacklist mask - ' . $bl;
    24792245                }
     
    24902256        }
    24912257        $banned_ips = self::get_banned_ips();
    2492         if ( is_array( self::$options['whitelist'] ) && self::is_whitelisted( $current_user_ip, self::$options['whitelist'] ) ) {
     2258        if ( is_array( self::$options['whitelist'] ) && Wf_sn_cf_Utils::is_whitelisted( $current_user_ip, self::$options['whitelist'] ) ) {
    24932259            return false;
    24942260        } elseif ( array_key_exists( $current_user_ip, $banned_ips ) ) {
     
    25132279                        // trim apostrophes
    25142280                        $subnet = trim( $subnet, "'" );
    2515                         if ( self::ipCIDRMatch( $current_user_ip, $subnet ) ) {
     2281                        if ( Wf_sn_cf_Utils::ipCIDRMatch( $current_user_ip, $subnet ) ) {
    25162282                            return 'IP in cloud blacklist range.';
    25172283                        }
     
    25192285                }
    25202286            }
     2287        }
     2288        // Allow known social/link-preview crawlers through Block IP Network (e.g. Facebook, LinkedIn).
     2289        if ( Wf_sn_cf_Utils::is_social_crawler_ua() ) {
     2290            return false;
    25212291        }
    25222292        // Checks if included in SecNin Global Block network
     
    25282298        }
    25292299        return false;
    2530     }
    2531 
    2532     /**
    2533      * Checks if an IP is whitelisted
    2534      *
    2535      * @author  Lars Koudal
    2536      * @since   v0.0.1
    2537      * @version v1.0.0  Monday, December 21st, 2020.
    2538      * @access  public static
    2539      * @param   mixed   $ip
    2540      * @param   mixed   $whitelist
    2541      * @return  boolean
    2542      */
    2543     public static function is_whitelisted( $ip, $whitelist ) {
    2544         foreach ( $whitelist as $key => $wip ) {
    2545             if ( strpos( $wip, '/' ) !== false ) {
    2546                 if ( self::ipCIDRMatch( $ip, $wip ) ) {
    2547                     return true;
    2548                 }
    2549             } else {
    2550                 if ( $ip === $wip ) {
    2551                     return true;
    2552                 }
    2553             }
    2554         }
    2555         return false;
    2556     }
    2557 
    2558     /**
    2559      * Update cloud firewall blocked IPs and update server IP to whitelist
    2560      *
    2561      * @author  Lars Koudal
    2562      * @since   v0.0.1
    2563      * @version v1.0.0  Monday, December 21st, 2020.
    2564      * @access  public static
    2565      * @return  void
    2566      */
    2567     public static function update_cloud_ips() {
    25682300    }
    25692301
     
    26512383            return;
    26522384        }
    2653         $msg = '<p class="message">' . self::$options['login_msg'] . '</p>';
    2654         echo $msg;
     2385        echo '<p class="message">' . esc_html( self::$options['login_msg'] ) . '</p>';
    26552386    }
    26562387
     
    33353066
    33363067    /**
    3337      * Return bad IPs from the central API
    3338      *
    3339      * @author  Lars Koudal
    3340      * @since   v0.0.1
    3341      * @version v1.0.0  Thursday, February 11th, 2021.
    3342      * @access  private static
    3343      * @return  boolean
    3344      */
    3345     private static function get_network_listips() {
    3346         // Check if the feature is enabled
    3347         if ( !self::$options['globalbannetwork'] ) {
    3348             wf_sn_el_modules::log_event( 'security_ninja', 'get_network_listips', 'Global network feature is disabled' );
    3349             return false;
    3350         }
    3351         $license_id = secnin_fs()->_get_license()->id;
    3352         $install_id = secnin_fs()->get_site()->id;
    3353         $site_private_key = secnin_fs()->get_site()->secret_key;
    3354         $nonce = date( 'Y-m-d' );
    3355         $pk_hash = hash( 'sha512', $site_private_key . '|' . $nonce );
    3356         $authentication_string = base64_encode( $pk_hash . '|' . $nonce );
    3357         $url = self::$central_api_url . 'listips/';
    3358         $response = wp_remote_get(
    3359             // Cannot use wp_safe_remote_get because we need to set the header
    3360             $url,
    3361             array(
    3362                 'headers'   => array(
    3363                     'Authorization' => $authentication_string,
    3364                 ),
    3365                 'body'      => array(
    3366                     'install_id' => $install_id,
    3367                     'license_id' => $license_id,
    3368                 ),
    3369                 'blocking'  => true,
    3370                 'timeout'   => 15,
    3371                 'sslverify' => false,
    3372             )
    3373          );
    3374         if ( is_wp_error( $response ) ) {
    3375             $error_message = $response->get_error_message();
    3376             wf_sn_el_modules::log_event( 'security_ninja', 'update_blocked_ips', 'Error getting IPs from network: "' . esc_html( $error_message ) . '"' );
    3377             return false;
    3378         } else {
    3379             $body = wp_remote_retrieve_body( $response );
    3380             $decoded = json_decode( $body );
    3381             $newips = 0;
    3382             if ( is_object( $decoded ) && isset( $decoded->ips ) ) {
    3383                 $newips = count( $decoded->ips );
    3384                 wf_sn_el_modules::log_event(
    3385                     'security_ninja',
    3386                     'update_blocked_ips',
    3387                     sprintf( esc_html__( 'Added/updated %1$s IPs from the blocklist.', 'security-ninja' ), $newips ),
    3388                     ''
    3389                 );
    3390             }
    3391             return $body;
    3392         }
    3393         return false;
    3394     }
    3395 
    3396     /**
    33973068     * display results
    33983069     *
  • security-ninja/trunk/modules/cloud-firewall/tabs/login-protection.php

    r3446591 r3461467  
    256256                                }
    257257                                ?>
    258                                 <input type="text" id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_new_login_url'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[new_login_url]'; ?>" value="<?php echo $options['new_login_url']; ?>" placeholder="<?php echo esc_attr($default_login_placeholder); ?>" class="regular-text">
     258                                <input type="text" id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_new_login_url'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[new_login_url]'; ?>" value="<?php echo esc_attr( $options['new_login_url'] ); ?>" placeholder="<?php echo esc_attr($default_login_placeholder); ?>" class="regular-text">
    259259                                <p><?php esc_html_e('Preview', 'security-ninja'); ?>: <code><?php echo esc_url(trailingslashit(site_url($options['new_login_url']))); ?></code></p>
    260260                            </td>
     
    297297                        <tr>
    298298                            <td colspan="2" class="fullwidth">
    299                                 <input type="number" id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_grace_period'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_grace_period]'; ?>" value="<?php echo $options['2fa_grace_period']; ?>" class="regular-text" data-1p-ignore>
     299                                <input type="number" id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_grace_period'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_grace_period]'; ?>" value="<?php echo esc_attr( $options['2fa_grace_period'] ); ?>" class="regular-text" data-1p-ignore>
    300300
    301301                                <input type="hidden" id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_enabled_timestamp'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_enabled_timestamp]'; ?>">
     
    346346                                        <li>
    347347                                            <label>
    348                                                 <input type="checkbox" id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_required_roles'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_required_roles][]'; ?>" value="<?php echo esc_attr($role); ?>" <?php echo $checked; ?>>
     348                                                <input type="checkbox" id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_required_roles'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_required_roles][]'; ?>" value="<?php echo esc_attr($role); ?>" <?php echo esc_attr( $checked ); ?>>
    349349                                                <?php echo esc_html($name); ?>
    350350                                            </label>
     
    399399                            <td colspan="2" class="fullwidth">
    400400
    401                                 <textarea id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_intro'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_intro]'; ?>" rows="3"><?php echo $options['2fa_intro']; ?></textarea>
     401                                <textarea id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_intro'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_intro]'; ?>" rows="3"><?php echo esc_textarea( $options['2fa_intro'] ); ?></textarea>
    402402                            </td>
    403403                        </tr>
     
    413413                        <tr>
    414414                            <td colspan="2" class="fullwidth">
    415                                 <textarea id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_enter_code'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_enter_code]'; ?>" rows="3"><?php echo $options['2fa_enter_code']; ?></textarea>
     415                                <textarea id="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '_2fa_enter_code'; ?>" name="<?php echo esc_attr(WF_SN_CF_OPTIONS_KEY) . '[2fa_enter_code]'; ?>" rows="3"><?php echo esc_textarea( $options['2fa_enter_code'] ); ?></textarea>
    416416                            </td>
    417417                        </tr>
  • security-ninja/trunk/modules/cloud-firewall/tabs/settings.php

    r3446591 r3461467  
    201201                                    $selected = in_array($key, $blocked_countries, true) ? ' selected="selected" ' : '';
    202202                            ?>
    203                                     <option value="<?php echo $key; ?>" <?php echo $selected; ?>><?php echo $gc . ' (' . $key . ')'; ?></option>
     203                                    <option value="<?php echo esc_attr( $key ); ?>" <?php echo esc_attr( $selected ); ?>><?php echo esc_html( $gc . ' (' . $key . ')' ); ?></option>
    204204                            <?php
    205205                                }
  • security-ninja/trunk/modules/core-scanner/core-scanner.php

    r3446591 r3461467  
    7171            add_action( 'wp_ajax_sn_core_restore_file_do', array(__NAMESPACE__ . '\\Wf_Sn_Cs', 'restore_file') );
    7272            add_action( 'wp_ajax_sn_core_run_scan', array(__NAMESPACE__ . '\\Wf_Sn_Cs', 'do_action_core_run_scan') );
     73            add_action( 'wp_ajax_sn_core_get_cached_results', array(__NAMESPACE__ . '\\Wf_Sn_Cs', 'get_cached_results') );
    7374            add_action( 'wp_ajax_sn_core_delete_all_unknowns', array(__NAMESPACE__ . '\\Wf_Sn_Cs', 'do_action_delete_all_unknowns') );
    7475        }
     
    168169            wp_register_script(
    169170                'sn-core-js',
    170                 $plugin_url . 'js/wf-sn-core-min.js',
     171                $plugin_url . 'js/wf-sn-core.js',
    171172                array('jquery'),
    172173                \WPSecurityNinja\Plugin\Utils::get_plugin_version(),
     
    186187                    'ajax_error'         => __( 'An error occurred during the AJAX request.', 'security-ninja' ),
    187188                    'please_wait'        => __( 'Please wait.', 'security-ninja' ),
     189                    'no_scan_yet'        => __( 'No scan made yet. Click "Scan Core Files" to run a scan.', 'security-ninja' ),
     190                    'loading'            => __( 'Loading...', 'security-ninja' ),
    188191                ),
    189192            );
     
    192195            wp_enqueue_style(
    193196                'sn-core-css',
    194                 $plugin_url . 'css/wf-sn-core-min.css',
     197                $plugin_url . 'css/wf-sn-core.css',
    195198                array(),
    196199                wf_sn::$version
     
    246249        }
    247250        die( wp_json_encode( $out ) );
     251    }
     252
     253    /**
     254     * AJAX: Return cached Core Scanner results only (no scan).
     255     * Used when the Core Scanner tab is focused to lazy-load last results.
     256     *
     257     * @return void
     258     */
     259    public static function get_cached_results() {
     260        check_ajax_referer( 'wf_sn_cs' );
     261        if ( !current_user_can( 'manage_options' ) ) {
     262            wp_send_json_error( array(
     263                'message' => __( 'You do not have sufficient permissions.', 'security-ninja' ),
     264            ) );
     265        }
     266        $results = get_option( 'wf_sn_cs_results', array() );
     267        if ( !is_array( $results ) || empty( $results['last_run'] ) || empty( $results['out'] ) ) {
     268            wp_send_json_success( array(
     269                'no_results' => true,
     270                'message'    => __( 'No scan made yet. Click "Scan Core Files" to run a scan.', 'security-ninja' ),
     271            ) );
     272        }
     273        $next_scan_ts = wp_next_scheduled( 'secnin_run_core_scanner' );
     274        if ( $next_scan_ts ) {
     275            $time_until = human_time_diff( time(), $next_scan_ts );
     276            $next_scan = sprintf(
     277                /* translators: %1$s is the date/time of next scan, %2$s is the time until next scan */
     278                esc_html__( '%1$s (%2$s from now)', 'security-ninja' ),
     279                date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $next_scan_ts ),
     280                $time_until
     281             );
     282        } else {
     283            $next_scan = __( 'No core scan currently scheduled.', 'security-ninja' );
     284        }
     285        wp_send_json_success( array(
     286            'out'           => $results['out'],
     287            'last_scan'     => ( isset( $results['last_scan'] ) ? $results['last_scan'] : '' ),
     288            'files_checked' => ( isset( $results['files_checked'] ) ? $results['files_checked'] : '' ),
     289            'wp_version'    => ( isset( $results['wp_version'] ) ? $results['wp_version'] : '' ),
     290            'next_scan'     => $next_scan,
     291        ) );
    248292    }
    249293
  • security-ninja/trunk/modules/core-scanner/js/wf-sn-core.js

    r3333048 r3461467  
    11/* globals jQuery:true, ajaxurl:true, wf_sn_cs:true */
    2 /*
    3 * Security Ninja - Scheduled Scanner add-on
    4 * (c) 2014. Web factory Ltd
    5 * 2019. Larsik Corp
    6 */
    7 
    8 jQuery( document ).ready(
    9     function ($) {
    10 
    11         // get the updates
    12         get_latest_update();
     2/**
     3 * Security Ninja - Core Scanner
     4 * (c) 2014 Web factory Ltd
     5 * 2019 Larsik Corp
     6 */
     7
     8jQuery( document ).ready( function( $ ) {
     9
     10        var coreScannerResultsLoading = false;
     11
     12        function loadCachedResults() {
     13            if ( window.location.hash !== '#sn_core' ) {
     14                return;
     15            }
     16            if ( ! $( '#wf-sn-core-scanner-response' ).length ) {
     17                return;
     18            }
     19            if ( coreScannerResultsLoading ) {
     20                return;
     21            }
     22            coreScannerResultsLoading = true;
     23            $( '#wf-sn-core-scanner-response' ).html( '<p class="description"><span class="spinner is-active"></span> ' + ( wf_sn_cs.strings.loading || 'Loading…' ) + '</p>' ).show();
     24            $.post(
     25                ajaxurl,
     26                {
     27                    action: 'sn_core_get_cached_results',
     28                    _ajax_nonce: wf_sn_cs.nonce
     29                },
     30                function (response) {
     31                    coreScannerResultsLoading = false;
     32                    var $container = $( '#wf-sn-core-scanner-response' );
     33                    if ( ! $container.length ) {
     34                        return;
     35                    }
     36                    if ( response.success && response.data ) {
     37                        if ( response.data.no_results ) {
     38                            $container.html( '<p class="description">' + ( response.data.message || wf_sn_cs.strings.no_scan_yet ) + '</p>' );
     39                            return;
     40                        }
     41                        if ( response.data.out ) {
     42                            $container.empty().append( response.data.out ).slideDown();
     43                        }
     44                        if ( response.data.last_scan ) {
     45                            $( '#wf-sn-core-scan-details #last_scan' ).html( response.data.last_scan ).slideDown();
     46                        }
     47                        if ( response.data.files_checked ) {
     48                            $( '#wf-sn-core-scan-details #files_checked' ).html( response.data.files_checked ).slideDown();
     49                        }
     50                        if ( response.data.wp_version ) {
     51                            $( '#wf-sn-core-scan-details #wp_version' ).html( response.data.wp_version ).slideDown();
     52                        }
     53                        if ( response.data.next_scan ) {
     54                            $( '#wf-sn-core-scan-details #next_scan' ).html( response.data.next_scan ).slideDown();
     55                        }
     56                    } else {
     57                        $container.html( '<p class="description">' + ( wf_sn_cs.strings.ajax_error || 'An error occurred.' ) + '</p>' );
     58                    }
     59                },
     60                'json'
     61            ).fail( function () {
     62                coreScannerResultsLoading = false;
     63                $( '#wf-sn-core-scanner-response' ).html( '<p class="description">' + ( wf_sn_cs.strings.ajax_error || 'An error occurred.' ) + '</p>' );
     64            } );
     65        }
     66
     67        function maybeLoadCoreScannerResults() {
     68            if ( window.location.hash === '#sn_core' ) {
     69                loadCachedResults();
     70            }
     71        }
     72
     73        $( window ).on( 'hashchange', maybeLoadCoreScannerResults );
     74        $( document ).on( 'click', '#wf-sn-tabs a[href="#sn_core"]', maybeLoadCoreScannerResults );
     75        maybeLoadCoreScannerResults();
    1376
    1477        $( 'button.sn-show-source' ).on(
     
    208271            }
    209272        );
    210     }
    211 );
     273} );
  • security-ninja/trunk/modules/events-logger/events-logger.php

    r3451093 r3461467  
    673673        wp_register_script(
    674674            'sn-el',
    675             $plugin_url . 'js/wf-sn-el-min.js',
     675            $plugin_url . 'js/wf-sn-el.js',
    676676            array('jquery', 'sn-el-datatables'),
    677677            wf_sn::$version,
  • security-ninja/trunk/modules/events-logger/js/wf-sn-el.js

    r3446591 r3461467  
    1 /* globals jQuery:true, ajaxurl:true, wf_sn_el:true, datatables_object:true */
    2 /*
    3  * Security Ninja - Events Logger add-on
     1/* globals jQuery:true, ajaxurl:true, wf_sn_el:true, datatables_object:true, eventstable:true */
     2/**
     3 * Security Ninja - Events Logger
    44 * (c) Web factory Ltd, 2015
    5  * Larsik Corp 2020 - 
     5 * Larsik Corp 2020 -
    66 */
    77
    8 jQuery(document).ready(function ($) {
    9     // Tab switching functionality
    10     $('#wf-sn-el-subtabs a').on('click', function(e) {
    11         e.preventDefault();
    12        
    13         // Remove active class from all tabs and content
    14         $('#wf-sn-el-subtabs a').removeClass('nav-tab-active');
    15         $('.wf-sn-el-subtab').hide();
    16        
    17         // Add active class to clicked tab
    18         $(this).addClass('nav-tab-active');
    19        
    20         // Show corresponding content
    21         var target = $(this).attr('href');
    22         $(target).show();
    23     });
     8var eventstable;
     9var eventsTableInitialized = false;
    2410
     11function isEventsTabFocused() {
     12    if ( ! jQuery( '#sn-el-datatable' ).length ) {
     13        return false;
     14    }
     15    if ( ! jQuery( '#wf-sn-tabs' ).length ) {
     16        return true;
     17    }
     18    return window.location.hash === '#sn_logger';
     19}
    2520
     21function initEventsDataTable( forceFocus ) {
     22    if ( eventsTableInitialized ) {
     23        return;
     24    }
     25    if ( ! jQuery( '#sn-el-datatable' ).length ) {
     26        return;
     27    }
     28    if ( ! forceFocus && ! isEventsTabFocused() ) {
     29        return;
     30    }
    2631
    27     // Initialize DataTable
    28     eventstable = jQuery('#sn-el-datatable').DataTable({
    29         "processing": true,
    30         "serverSide": true,
    31         "pageLength": 25,
    32         "ajax": {
    33             "url": ajaxurl,
    34             "type": "POST",
    35             "data": function(d) {
    36                 d.action = "get_events_data";
    37                 d.nonce = datatables_object.nonce;
    38                 d.action_filter = jQuery('#sn-el-action-filter').val();
    39             },
    40             "error": function(xhr, error, code) {
    41                 var errorMsg = "<strong>Error loading data:</strong><br>" +
    42                              "Status: " + xhr.status + " (" + xhr.statusText + ")<br>" +
    43                              "Error: " + error + "<br>" +
    44                              "Code: " + code + "<br>" +
    45                              "Response: " + xhr.responseText;
    46                 jQuery('#datatable-error').html(errorMsg).show();
    47             }
    48         },
    49         "columns": [
    50             { "data": "timestamp", "title": "Time" },
    51             { "data": "action", "title": "Action" },
    52             { "data": "user_id", "title": "User" },
    53             { "data": "description", "title": "Event" },
    54             { "data": "details", "title": "Details", "orderable": false }
    55         ],
    56         "order": [[ 0, "desc" ]],
    57         "columnDefs": [{
    58             "targets": 4,
    59             "data": null,
    60             "defaultContent": "<button>Detail</button>"
    61         }]
    62     });
     32    eventsTableInitialized = true;
     33    eventstable = jQuery( '#sn-el-datatable' ).DataTable( {
     34        processing: true,
     35        language: {
     36            processing: 'Loading…'
     37        },
     38        serverSide: true,
     39        pageLength: 25,
     40        ajax: {
     41            url: ajaxurl,
     42            type: 'POST',
     43            data: function( d ) {
     44                d.action = 'get_events_data';
     45                d.nonce = datatables_object.nonce;
     46                d.action_filter = jQuery( '#sn-el-action-filter' ).val();
     47            },
     48            error: function( xhr, error, code ) {
     49                var errorMsg = '<strong>Error loading data:</strong><br>Status: ' + xhr.status + ' (' + xhr.statusText + ')<br>Error: ' + error + '<br>Code: ' + code + '<br>Response: ' + xhr.responseText;
     50                jQuery( '#datatable-error' ).html( errorMsg ).show();
     51            }
     52        },
     53        columns: [
     54            { data: 'timestamp', title: 'Time' },
     55            { data: 'action', title: 'Action' },
     56            { data: 'user_id', title: 'User' },
     57            { data: 'description', title: 'Event' },
     58            { data: 'details', title: 'Details', orderable: false }
     59        ],
     60        order: [[ 0, 'desc' ]],
     61        columnDefs: [{
     62            targets: 4,
     63            data: null,
     64            defaultContent: '<button>Detail</button>'
     65        }]
     66    } );
    6367
    64     // Load available actions for the filter dropdown
    65     function loadActionFilter() {
    66         jQuery.post(ajaxurl, {
    67             action: 'get_events_actions',
    68             nonce: datatables_object.nonce
    69         }, function(response) {
    70             if (response.success && response.data.actions) {
    71                 var select = jQuery('#sn-el-action-filter');
    72                 select.empty(); // Clear all options including the loading one
    73                
    74                 // Add "All Actions" option first
    75                 select.append('<option value="">All Actions</option>');
    76                
    77                 // Add all the action options
    78                 response.data.actions.forEach(function(action) {
    79                     select.append('<option value="' + action + '">' + action + '</option>');
    80                 });
    81             }
    82         });
    83     }
     68    function loadActionFilter() {
     69        jQuery.post( ajaxurl, {
     70            action: 'get_events_actions',
     71            nonce: datatables_object.nonce
     72        }, function( response ) {
     73            if ( response.success && response.data.actions ) {
     74                var select = jQuery( '#sn-el-action-filter' );
     75                select.empty();
     76                select.append( '<option value="">All Actions</option>' );
     77                response.data.actions.forEach( function( action ) {
     78                    select.append( '<option value="' + action + '">' + action + '</option>' );
     79                } );
     80            }
     81        } );
     82    }
     83    loadActionFilter();
    8484
    85     // Load actions on page load
    86     loadActionFilter();
     85    jQuery( '#sn-el-action-filter' ).on( 'change', function() {
     86        eventstable.ajax.reload();
     87    } );
    8788
    88     // Handle action filter change
    89     jQuery('#sn-el-action-filter').on('change', function() {
    90         eventstable.ajax.reload();
    91     });
     89    jQuery( '#sn-el-reset-filter' ).on( 'click', function() {
     90        jQuery( '#sn-el-action-filter' ).val( '' );
     91        eventstable.ajax.reload();
     92    } );
    9293
    93     // Handle reset filter button
    94     jQuery('#sn-el-reset-filter').on('click', function() {
    95         jQuery('#sn-el-action-filter').val('');
    96         eventstable.ajax.reload();
    97     });
     94    jQuery( '#sn-el-datatable tbody' ).on( 'click', 'button', function( e ) {
     95        e.preventDefault();
     96        var tr = jQuery( this ).closest( 'tr' );
     97        var row = eventstable.row( tr );
     98        if ( row.child.isShown() ) {
     99            row.child.hide();
     100            tr.removeClass( 'shown' );
     101            jQuery( this ).removeClass( 'open' );
     102        } else {
     103            var details = tr.find( '.details-content' ).html();
     104            row.child( details ).show();
     105            tr.addClass( 'shown' );
     106            jQuery( this ).addClass( 'open' );
     107        }
     108    } );
     109}
    98110
    99     // Child rows in the event log table
    100     // Expand details if available
    101     $('#sn-el-datatable tbody').on('click', 'button', function (e) {
    102         e.preventDefault();
    103         var tr = $(this).closest('tr');
    104         var row = eventstable.row(tr);
    105         if (row.child.isShown()) {
    106             // This row is already open - close it
    107             row.child.hide();
    108             tr.removeClass('shown');
    109             $(this).removeClass('open');
    110         } else {
    111             // Open this row
    112             var details = tr.find('.details-content').html();
    113             row.child(details).show();
    114             tr.addClass('shown');
    115             $(this).addClass('open');
    116         }
    117     });
     111jQuery( document ).ready( function( $ ) {
     112    // Tab switching (Events Logger subtabs).
     113    $( '#wf-sn-el-subtabs a' ).on( 'click', function( e ) {
     114        e.preventDefault();
     115        $( '#wf-sn-el-subtabs a' ).removeClass( 'nav-tab-active' );
     116        $( '.wf-sn-el-subtab' ).hide();
     117        $( this ).addClass( 'nav-tab-active' );
     118        var target = $( this ).attr( 'href' );
     119        $( target ).show();
     120    } );
    118121
    119     // truncate log table
    120     $('#sn-el-truncate').on('click', function (e) {
    121         e.preventDefault();
     122    function maybeInitEventsDataTable() {
     123        if ( window.location.hash !== '#sn_logger' ) {
     124            return;
     125        }
     126        initEventsDataTable( true );
     127    }
    122128
    123         var answer = confirm("Are you sure you want to delete all log entries?"); // @i8n
    124         if (answer) {
    125             var data = {
    126                 action: 'sn_el_truncate_log',
    127                 _ajax_nonce: wf_sn_el.nonce
    128             };
    129             $.post(ajaxurl, data, function (response) {
    130                 if (!response) {
    131                     alert('Bad AJAX response. Please reload the page.'); // @i8n
    132                 } else {
    133                     alert('All log entries have been deleted.'); // @i8n
    134                     window.location.reload();
    135                 }
    136             });
    137         }
    138     });
    139 });
     129    jQuery( window ).on( 'hashchange', maybeInitEventsDataTable );
     130    jQuery( document ).on( 'click', '#wf-sn-tabs a[href="#sn_logger"]', function() {
     131        maybeInitEventsDataTable();
     132    } );
     133    maybeInitEventsDataTable();
     134
     135    jQuery( '#sn-el-truncate' ).on( 'click', function( e ) {
     136        e.preventDefault();
     137        var answer = confirm( 'Are you sure you want to delete all log entries?' );
     138        if ( answer ) {
     139            var data = {
     140                action: 'sn_el_truncate_log',
     141                _ajax_nonce: wf_sn_el.nonce
     142            };
     143            $.post( ajaxurl, data, function( response ) {
     144                if ( ! response ) {
     145                    alert( 'Bad AJAX response. Please reload the page.' );
     146                } else {
     147                    alert( 'All log entries have been deleted.' );
     148                    window.location.reload();
     149                }
     150            } );
     151        }
     152    } );
     153} );
  • security-ninja/trunk/modules/overview/class-wf-sn-overview-tab.php

    r3446591 r3461467  
    3636            ?></div>
    3737      <div class="secscore-value"><?php
    38             echo $scores['score'];
     38            echo absint( $scores['score'] );
    3939            ?>%</div>
    4040      </div>
    4141      <div id="secscorerowrow">
    4242      <div class="inner" style="width:<?php
    43             echo $scores['score'];
     43            echo esc_attr( (string) absint( $scores['score'] ) );
    4444            ?>%;"></div>
    4545      </div>
    4646      <div id="secscore-details">
    4747      <div class="secscore-passed"><span class="det-count"><?php
    48             echo $scores['good'];
     48            echo absint( $scores['good'] );
    4949            ?></span><span class="det"><?php
    5050            echo esc_html__( 'Tests passed', 'security-ninja' );
    5151            ?></span></div>
    5252      <div class="secscore-warning"><span class="det-count"><?php
    53             echo $scores['warning'];
     53            echo absint( $scores['warning'] );
    5454            ?></span><span class="det"><?php
    5555            echo esc_html__( 'Warnings', 'security-ninja' );
    5656            ?></span></div>
    5757      <div class="secscore-failed"><span class="det-count"><?php
    58             echo $scores['bad'];
     58            echo absint( $scores['bad'] );
    5959            ?></span><span class="det"><?php
    6060            echo esc_html__( 'Tests failed', 'security-ninja' );
     
    207207        );
    208208        $actions_to_track = $free_actions_to_track;
    209         $show_pro_ad = true;
    210209        // Show firewall summary for all users (free and premium)
    211210        ?>
     
    232231        $results = $wpdb->get_results( $query, ARRAY_A );
    233232        if ( !empty( $results ) ) {
    234             // If we have results, don't show the upgrade ad
    235             $show_pro_ad = false;
    236233            echo '<div class="action-counts">';
    237234            echo '<h4>' . esc_html__( 'Action Counts', 'security-ninja' ) . '</h4>';
     
    289286        </div>
    290287        <?php
     288        $show_pro_ad = true;
    291289        if ( $show_pro_ad ) {
    292290            ?>
     
    344342            echo esc_html__( 'Put your own agency branding on the plugin.', 'security-ninja' );
    345343            ?></div>
    346         </div>
     344   
    347345        <?php
    348346            $url = 'https://wpsecurityninja.com/pricing/';
     
    366364            ), $url );
    367365            ?>
     366
     367       
     368        </div>
     369        <div>
    368370        <p style="margin-top: 10px;text-align: center;">
    369371        <a href="<?php
    370372            echo esc_url( $pricing_url );
    371             ?>" class="wf-sn-button button button-secondary" target="_blank"><?php
     373            ?>" class="wf-sn-button button" target="_blank"><?php
    372374            echo esc_html__( 'Explore WP Security Ninja Pro now!', 'security-ninja' );
    373375            ?></a><br><small>or try our <a href="<?php
     
    375377            ?>" class="" target="_blank">14 days FREE trial &raquo;</a></small>
    376378        </p>
    377        
    378379        </div>
    379380        </div>
  • security-ninja/trunk/modules/vulnerabilities/class-wf-sn-vu.php

    r3457609 r3461467  
    491491            $content_length = ( isset( $headers['content-length'] ) ? (int) $headers['content-length'] : strlen( $body ) );
    492492            $result_info['bytes_downloaded'] = $content_length;
    493             // Extract ETag and Last-Modified from response headers.
    494             // Sanitize header values before storing.
    495493            $received_etag = ( isset( $headers['etag'] ) ? sanitize_text_field( $headers['etag'] ) : '' );
    496494            $received_last_modified = ( isset( $headers['last-modified'] ) ? sanitize_text_field( $headers['last-modified'] ) : '' );
    497             // Robust gzip detection: check Content-Encoding header and magic bytes.
    498495            $content_encoding = ( isset( $headers['content-encoding'] ) ? strtolower( $headers['content-encoding'] ) : '' );
    499496            $is_gzipped = false;
  • security-ninja/trunk/readme.txt

    r3458434 r3461467  
    77Requires at least: 4.7
    88Tested up to: 6.9.1
    9 Stable tag: 5.266
     9Stable tag: 5.267
    1010Requires PHP: 7.4
    1111
     
    333333== Changelog ==
    334334
     335= 5.267 =
     336* 2026-02-13
     337* IMPROVED: Litespeed servers - Added documentation and in-app notices for all security headers (CSP, X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security, Referrer-Policy, Permissions-Policy). LiteSpeed users can add headers directly to .htaccess using the examples in each test description. Thank you Tom for the feedback.
     338* FIX: Events Logger, Overview, and Visitor Log – Country flags now correctly show the event/visitor IP's country instead of the logged-in admin's IP when the site is behind Cloudflare or similar proxies.
     339* Improved: Core Scanner - Interface loads faster with tabs lazy-loading content in different tabs.
     340* IMPROVED: Firewall – When "Block IP Network" is enabled, known social and link-preview crawlers (e.g. Facebook, LinkedIn, Twitter) are no longer blocked by default. Link previews when you share your site on social networks now work without having to whitelist IPs.
     341
    335342= 5.266 =
    336343* 2026-02-10
     
    461468
    462469== Upgrade Notice ==
    463 5.258
     4705.267
    464471Recommended update.
  • security-ninja/trunk/security-ninja.php

    r3458434 r3461467  
    66Description: Check your site for security vulnerabilities and get precise suggestions for corrective actions on passwords, user accounts, file permissions, database security, version hiding, plugins, themes, security headers and other security aspects.
    77Author: WP Security Ninja
    8 Version: 5.266
     8Version: 5.267
    99Author URI: https://wpsecurityninja.com/
    1010License: GPLv3
     
    114114    // File viewer
    115115    include_once WF_SN_PLUGIN_DIR . 'modules/file-viewer/class-secnin-file-viewer.php';
     116    include_once WF_SN_PLUGIN_DIR . 'modules/cloud-firewall/class-wf-sn-cf-utils.php';
    116117    include_once WF_SN_PLUGIN_DIR . 'modules/cloud-firewall/cloud-firewall.php';
    117118    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-utils.php';
     
    252253            $resultssofar = get_option( 'security_tests_results', array() );
    253254            $set_time_limit = set_time_limit( 200 );
    254             $last_test_run = ( isset( $resultssofar['last_test_run'] ) ? $resultssofar['last_test_run'] : '' );
    255255            $resultssofar['last_run'] = time();
    256256            foreach ( $security_tests as $test_name => $test ) {
     
    713713                );
    714714            }
    715             // REST API submenu is handled by Wf_Sn_Rest_Api_Admin class
    716715        }
    717716
  • security-ninja/trunk/sn-tests-description.php

    r3391792 r3461467  
    4646
    4747        <p><?php esc_html_e( 'You can also add this to your .htaccess file', 'security-ninja' ); ?></p>
     48        <p><strong><?php esc_html_e( 'LiteSpeed servers:', 'security-ninja' ); ?></strong> <?php esc_html_e( 'The PHP header interface may not work correctly on LiteSpeed. If this header is not appearing in your response, add it directly to your .htaccess file using the example below.', 'security-ninja' ); ?></p>
    4849<pre>#BEGIN - Forces only HTTPS
    4950    &lt;IfModule mod_headers.c&gt;
     
    7071
    7172        <p><?php esc_html_e( 'This example forces a browser to only load JavaScript .js files from your own website. Warning: Inline code will stop working. Add this to your .htaccess file', 'security-ninja' ); ?></p>
     73        <p><strong><?php esc_html_e( 'LiteSpeed servers:', 'security-ninja' ); ?></strong> <?php esc_html_e( 'The PHP header interface may not work correctly on LiteSpeed. If CSP headers are not appearing in your response, add the policy directly to your .htaccess file using the example below.', 'security-ninja' ); ?></p>
    7274<pre>#BEGIN - Only allow browsers to load .js files from this website
    7375    # Use Content-Security-Policy-Report-Only to test settings before using Content-Security-Policy.
     
    105107
    106108            <p><?php esc_html_e( 'You can also add this to your .htaccess file', 'security-ninja' ); ?></p>
     109            <p><strong><?php esc_html_e( 'LiteSpeed servers:', 'security-ninja' ); ?></strong> <?php esc_html_e( 'The PHP header interface may not work correctly on LiteSpeed. If this header is not appearing in your response, add it directly to your .htaccess file using the example below.', 'security-ninja' ); ?></p>
    107110<pre>#BEGIN - Prevent page-framing and click-jacking
    108111    &lt;IfModule mod_headers.c&gt;
     
    139142                <pre>header('X-Content-Type-Options: nosniff');</pre>
    140143                <p><?php esc_html_e( 'You can also add this to your .htaccess file', 'security-ninja' ); ?></p>
     144                <p><strong><?php esc_html_e( 'LiteSpeed servers:', 'security-ninja' ); ?></strong> <?php esc_html_e( 'The PHP header interface may not work correctly on LiteSpeed. If this header is not appearing in your response, add it directly to your .htaccess file using the example below.', 'security-ninja' ); ?></p>
    141145<pre>#BEGIN - Prevent code in unexpected files
    142146    &lt;IfModule mod_headers.c&gt;
     
    164168                <p><?php esc_html_e( 'NOTE: This example disables everything, so if you have website that uses some of the features please check the link to Mozilla on more details on how to finetune.', 'security-ninja' ); ?></p>
    165169                <p><?php esc_html_e( 'You can also add this to your .htaccess file', 'security-ninja' ); ?></p>
     170                <p><strong><?php esc_html_e( 'LiteSpeed servers:', 'security-ninja' ); ?></strong> <?php esc_html_e( 'The PHP header interface may not work correctly on LiteSpeed. If this header is not appearing in your response, add it directly to your .htaccess file using the example below.', 'security-ninja' ); ?></p>
    166171
    167172<pre>
     
    195200                <pre>header('Referrer-Policy: same-origin');</pre>
    196201                <p><?php esc_html_e( 'You can also add this to your .htaccess file', 'security-ninja' ); ?></p>
     202                <p><strong><?php esc_html_e( 'LiteSpeed servers:', 'security-ninja' ); ?></strong> <?php esc_html_e( 'The PHP header interface may not work correctly on LiteSpeed. If this header is not appearing in your response, add it directly to your .htaccess file using the example below.', 'security-ninja' ); ?></p>
    197203
    198204<pre>
  • security-ninja/trunk/vendor/composer/installed.php

    r3458434 r3461467  
    44        'pretty_version' => 'dev-master',
    55        'version' => 'dev-master',
    6         'reference' => '1dae68d4c699222b44a1a04eafae31852708e311',
     6        'reference' => 'e1e847a9ec5a9a5db9b8bb6deeabff88f6a7d58c',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    149149            'pretty_version' => 'dev-master',
    150150            'version' => 'dev-master',
    151             'reference' => '1dae68d4c699222b44a1a04eafae31852708e311',
     151            'reference' => 'e1e847a9ec5a9a5db9b8bb6deeabff88f6a7d58c',
    152152            'type' => 'library',
    153153            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.