Plugin Directory

Changeset 3734917


Ignore:
Timestamp:
10/08/2026 03:02:05 PM (2 weeks ago)
Author:
devdome
Message:

Malware Scanner 1.3.10: bug-report pill on Overview before the first scan; shared DevDome library 1.7.12 (Cloudflare address header trusted only from a Cloudflare edge)

Location:
devdome-malware-scanner
Files:
71 added
9 edited

Legend:

Unmodified
Added
Removed
  • devdome-malware-scanner/trunk/admin/admin.php

    r3723150 r3734917  
    107107.dd-app .mw-pill-num { font-weight:700; color:#4b5563; }
    108108.dd-app .mw-pill.is-on .mw-pill-num { color:#4f46e5; }
     109/* Bug pill (owner 2026-09-23, built 1.3.10): one pill on the Overview before the first completed scan; the filter pill's look. */
     110.dd-app .mw-bugpill { display:inline-flex; align-items:center; gap:4px; margin-top:12px; max-width:100%; }
     111.dd-app .mw-bugpill-btn { display:inline-flex; align-items:center; gap:7px; border:1px solid #c7d2fe; background:#eef2ff; padding:5px 12px; border-radius:999px; font-size:12.5px; font-weight:600; color:#4338ca; cursor:pointer; line-height:1.3; text-align:left; }
     112.dd-app .mw-bugpill-btn:hover { background:#e0e7ff; }
     113.dd-app .mw-bugpill-btn .dashicons { font-size:14px; width:14px; height:14px; }
     114.dd-app .mw-bugpill-x { border:0; background:none; padding:2px 6px; color:#9ca3af; font-size:16px; line-height:1; cursor:pointer; }
     115.dd-app .mw-bugpill-x:hover { color:#4b5563; }
    109116/* Data lists = DESIGN.md section 19 (.dd-list): the Analytics dashboard table, 1:1. */
    110117.dd-app .dd-list-wrap { overflow-x:auto; border-top:1px solid #e5e7eb; }
    … …  
    550557                    <input type="email" id="devdmalw-modal-email" maxlength="100" style="width:100%;font-size:13px;" placeholder="you@example.com">
    551558                </div>
     559                <?php // Bug report from the Overview pill (1.3.10): the note is the report text, sent through the core's Report this error path. ?>
     560                <div id="devdmalw-bug-form" hidden style="margin:-4px 20px 14px;">
     561                    <label for="devdmalw-bug-note" style="display:block;font-size:12px;font-weight:600;color:#374151;margin:0 0 4px;"><?php esc_html_e('What went wrong?', 'devdome-malware-scanner'); ?></label>
     562                    <textarea id="devdmalw-bug-note" rows="4" maxlength="2000" style="width:100%;font-size:13px;margin:0;" placeholder="<?php esc_attr_e('What did you do, what did you expect, what happened instead?', 'devdome-malware-scanner'); ?>"></textarea>
     563                </div>
    552564                <div class="dd-modal-foot"><span class="ddc-report" id="devdmalw-modal-report" hidden style="margin-right:auto;margin-left:0;"><button type="button" class="ddc-report-btn" id="devdmalw-modal-report-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 2 11 13"/><path d="M22 2 15 22l-4-9-9-4z"/></svg><?php esc_html_e('Report this error', 'devdome-malware-scanner'); ?></button></span><button type="button" class="dd-btn" id="devdmalw-modal-cancel" data-mw-modal-close><?php esc_html_e('Cancel', 'devdome-malware-scanner'); ?></button><button type="button" class="dd-btn-primary" id="devdmalw-modal-ok"><?php esc_html_e('Confirm', 'devdome-malware-scanner'); ?></button></div>
    553565            </div>
    … …  
    662674    }
    663675    $last_ago  = $done ? human_time_diff(strtotime($done->completed_at . ' UTC'), time()) : '';
     676    // The pill shows only while no scan has completed and nobody dismissed it (option, per site). Not a trust decision,
     677    // so a plain get_option() is enough here.
     678    $bug_pill  = !$scanned && !get_option('devdmalw_bug_pill_off');
    664679    ?>
    665680    <?php
    … …  
    708723                        <?php if ($caveat !== '') : ?>
    709724                            <span class="mw-verdict-p" id="devdmalw-verdict-caveat" style="font-size:12.5px;color:#b45309;margin:6px 0 0;"><span class="dashicons dashicons-warning" style="font-size:14px;width:14px;height:14px;vertical-align:text-bottom;"></span> <?php echo esc_html($caveat); ?></span>
     725                        <?php endif; ?>
     726                        <?php if ($bug_pill) : // owner 2026-09-23: "Found a bug?" pill on the first screen, gone after the first completed scan or a dismiss ?>
     727                            <span class="mw-bugpill" id="devdmalw-bugpill"><button type="button" class="mw-bugpill-btn" id="devdmalw-bugpill-btn"><span class="dashicons dashicons-flag" aria-hidden="true"></span><?php esc_html_e('Found a bug? Report it, we fix it within 24h', 'devdome-malware-scanner'); ?></button><button type="button" class="mw-bugpill-x" id="devdmalw-bugpill-x" aria-label="<?php esc_attr_e('Dismiss', 'devdome-malware-scanner'); ?>">&times;</button></span>
    710728                        <?php endif; ?>
    711729                    </div>
    … …  
    20572075}
    20582076add_action('wp_ajax_devdmalw_qact', 'devdmalw_ajax_quarantine_action');
     2077
     2078/** The Overview bug pill was dismissed: remember it per site (proved write, DESIGN.md 24.5). */
     2079function devdmalw_ajax_bug_pill_off()
     2080{
     2081    devdmalw_ajax_guard();
     2082    if (!devdmalw_option_write('devdmalw_bug_pill_off', 1)) {
     2083        wp_send_json_error('The setting could not be saved. The pill stays until it is.', 500);
     2084    }
     2085    devdmalw_json_success();
     2086}
     2087add_action('wp_ajax_devdmalw_bug_pill_off', 'devdmalw_ajax_bug_pill_off');
  • devdome-malware-scanner/trunk/assets/admin.js

    r3723150 r3734917  
    7272        if (!modal) { return; }
    7373        modal.setAttribute('hidden', 'hidden');
     74        bugOff(); // the bug note box never survives a close (1.3.10)
    7475        var cancelled = modalPending ? modalCancelHook : null;
    7576        modalPending = null;
    … …  
    8485        opts = opts || {};
    8586        if (modalReport) { modalReport.setAttribute('hidden', 'hidden'); }
     87        bugOff();
    8688        // Easy mode (owner 2026-09-15): the question is the title, the body is ONE line, the mechanics fold away
    8789        // under "What happens", and the button says what it does.
    … …  
    127129        if (modalForm) { modalForm.setAttribute('hidden', 'hidden'); }
    128130        if (modalReport) { modalReport.setAttribute('hidden', 'hidden'); }
     131        bugOff();
    129132        modalText.textContent = text;
    130133        modalOk.textContent = 'OK';
    … …  
    145148        if (modalDetail) { modalDetail.setAttribute('hidden', 'hidden'); }
    146149        if (modalForm) { modalForm.setAttribute('hidden', 'hidden'); }
     150        bugOff();
    147151        modalText.textContent = text;
    148152        modalOk.textContent = 'OK';
    … …  
    153157            if (window.devdcorev1ReportError) { modalReport.removeAttribute('hidden'); } else { modalReport.setAttribute('hidden', 'hidden'); }
    154158            var note = modalReport.querySelector('.ddc-report-note'); if (note) { note.remove(); }
    155             if (modalReportBtn) { modalReportBtn.disabled = false; modalReportBtn.textContent = 'Report this error'; }
     159            var rb = freshReportBtn(); // a bug report still in flight must not label this error "Reported" (Codex 1.3.10 r2)
     160            if (rb) { rb.disabled = false; rb.textContent = 'Report this error'; }
    156161        }
    157162        modal.removeAttribute('hidden');
    … …  
    159164        modalOk.focus();
    160165    }
    161     if (modalReportBtn) {
    162         modalReportBtn.addEventListener('click', function () {
     166    // A fresh button per bug dialog (Codex 1.3.10 r1): a report still in flight from an earlier dialog updates the
     167    // detached node it was given, never the button of the dialog that is open now.
     168    function freshReportBtn() {
     169        if (!modalReportBtn || !modalReportBtn.parentNode) { return modalReportBtn; }
     170        var copy = modalReportBtn.cloneNode(true);
     171        modalReportBtn.parentNode.replaceChild(copy, modalReportBtn);
     172        modalReportBtn = copy;
     173        return copy;
     174    }
     175    if (modalReport && modalReportBtn) {
     176        modalReport.addEventListener('click', function (e) {
     177            if (!e.target || !e.target.closest || !e.target.closest('.ddc-report-btn')) { return; }
    163178            if (!window.devdcorev1ReportError) { return; }
     179            if (bugMode) {
     180                // The Overview bug pill (1.3.10): the typed note IS the report; the core refuses an empty one, so do we.
     181                var txt = bugNote ? bugNote.value.replace(/^\s+|\s+$/g, '') : '';
     182                if (txt === '') { if (bugNote) { bugNote.focus(); } return; }
     183                window.devdcorev1ReportError({ plugin: 'devdome-malware-scanner', version: data.version || '', error: txt, screen: 'Malware Scanner bug report (Overview pill)' }, modalReportBtn);
     184                return;
     185            }
    164186            window.devdcorev1ReportError({ plugin: 'devdome-malware-scanner', version: data.version || '', error: lastNotice, screen: 'Malware Scanner action' }, modalReportBtn);
     187        });
     188    }
     189    /* Bug pill on the Overview (owner 2026-09-23, built 1.3.10): the dialog takes a note and sends it through the core's
     190       Report this error path (nothing new on the server); the x hides the pill for this site through a proved option write. */
     191    var bugForm = document.getElementById('devdmalw-bug-form');
     192    var bugNote = document.getElementById('devdmalw-bug-note');
     193    var bugMode = false;
     194    function bugOff() { bugMode = false; if (bugForm) { bugForm.setAttribute('hidden', 'hidden'); } }
     195    function bugDialog() {
     196        if (!modal || !bugForm || !window.devdcorev1ReportError) { return; }
     197        modalTitle.textContent = 'Report a bug';
     198        if (modalDetail) { modalDetail.setAttribute('hidden', 'hidden'); }
     199        if (modalForm) { modalForm.setAttribute('hidden', 'hidden'); }
     200        modalText.textContent = 'Tell us what went wrong. DevDome support reads every report and fixes bugs within 24 hours. The report carries your note, the plugin, WordPress and PHP versions, the last scanner log lines with secrets removed, your site address and the admin e-mail for the answer.';
     201        if (bugNote) { bugNote.value = ''; }
     202        bugForm.removeAttribute('hidden');
     203        bugMode = true;
     204        if (modalReport) {
     205            modalReport.removeAttribute('hidden');
     206            var note = modalReport.querySelector('.ddc-report-note'); if (note) { note.remove(); }
     207            var btn = freshReportBtn();
     208            if (btn) { btn.disabled = false; btn.textContent = 'Send report'; }
     209        }
     210        modalOk.textContent = 'Close';
     211        modalCancel.style.display = 'none';
     212        modal.removeAttribute('hidden');
     213        modalPending = null;
     214        if (bugNote) { bugNote.focus(); }
     215    }
     216    var bugPill = document.getElementById('devdmalw-bugpill');
     217    var bugPillBtn = document.getElementById('devdmalw-bugpill-btn');
     218    var bugPillX = document.getElementById('devdmalw-bugpill-x');
     219    if (bugPillBtn) { bugPillBtn.addEventListener('click', bugDialog); }
     220    if (bugPillX && bugPill) {
     221        bugPillX.addEventListener('click', function () {
     222            bugPillX.disabled = true;
     223            post('devdmalw_bug_pill_off', {}, function (err, j) {
     224                if (err || !j || !j.success) { bugPillX.disabled = false; notice(err || !j ? 'Request failed.' : String(j.data || 'unknown error')); return; }
     225                if (bugPill.parentNode) { bugPill.parentNode.removeChild(bugPill); }
     226            });
    165227        });
    166228    }
  • devdome-malware-scanner/trunk/devdome-malware-scanner.php

    r3723150 r3734917  
    44Plugin URI: https://devdome.com/
    55Description: Standalone WordPress malware, backdoor and persistence scanner. Verifies core and plugin integrity, hunts unknown files, obfuscated code, database injections, rogue admins and WP-Cron persistence, with safe quarantine and trusted repair.
    6 Version: 1.3.9
     6Version: 1.3.10
    77Author: DevDome
    88Author URI: https://devdome.com
    … …  
    2424}
    2525
    26 define('DEVDMALW_VERSION', '1.3.9');
     26define('DEVDMALW_VERSION', '1.3.10');
    2727define('DEVDMALW_RULES_VERSION', '1');
    2828define('DEVDMALW_DIR', plugin_dir_path(__FILE__));
  • devdome-malware-scanner/trunk/lib/devdome-core/devdome-core.php

    r3723150 r3734917  
    2121}
    2222define('DEVDCOREV1_LOADED', true);
    23 define('DEVDCOREV1_VERSION', '1.7.10');
     23define('DEVDCOREV1_VERSION', '1.7.12');
    2424
    2525if (!defined('DEVDCOREV1_FEED_ENDPOINT')) {
    … …  
    295295    add_action('rest_api_init', 'devdcorev1_beacon_rest_init');
    296296
     297    /**
     298     * Cloudflare edge ranges, copied from https://www.cloudflare.com/ips-v4/ and /ips-v6/ on 2026-10-02
     299     * (core 1.7.12). Shipped, never fetched at runtime; refreshed with core releases.
     300     */
     301    function devdcorev1_cloudflare_cidrs()
     302    {
     303        return array(
     304            '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18',
     305            '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17',
     306            '162.158.0.0/15', '104.16.0.0/13', '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22',
     307            '2400:cb00::/32', '2606:4700::/32', '2803:f800::/32', '2405:b500::/32', '2405:8100::/32',
     308            '2a06:98c0::/29', '2c0f:f248::/32',
     309        );
     310    }
     311
     312    /** True only when the TCP peer (REMOTE_ADDR) is a Cloudflare edge. Cached per request. */
     313    function devdcorev1_peer_is_cloudflare()
     314    {
     315        static $cached = null;
     316        if ($cached !== null) {
     317            return $cached;
     318        }
     319        $peer = isset($_SERVER['REMOTE_ADDR']) ? filter_var(trim(sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']))), FILTER_VALIDATE_IP) : false;
     320        if (false === $peer) {
     321            return $cached = false;
     322        }
     323        $idx = devdcorev1_build_cidr_index(devdcorev1_cloudflare_cidrs());
     324        return $cached = devdcorev1_ip_in_cidr_index($peer, $idx['v4'], $idx['v6']);
     325    }
     326
    297327    /** Client IP for the beacon (Cloudflare-aware). */
    298328    function devdcorev1_request_ip()
    299329    {
    300         // CF-Connecting-IP is believed only when a plugin vouches that the peer is Cloudflare (the bot-protection
    301         // plugin checks REMOTE_ADDR against the published ranges); otherwise anyone could name a victim address.
    302         $trust_cf = (bool) apply_filters('devdcorev1_trust_cf_ip', false);
     330        // CF-Connecting-IP is believed only when the peer really is Cloudflare: the core checks REMOTE_ADDR against the
     331        // shipped edge ranges (core 1.7.12, DeepSeek core round 1), and a plugin may still vouch through the filter.
     332        // Otherwise anyone could name a victim address by sending the header themselves.
     333        $trust_cf = (bool) apply_filters('devdcorev1_trust_cf_ip', devdcorev1_peer_is_cloudflare());
    303334        foreach ($trust_cf ? array('HTTP_CF_CONNECTING_IP', 'REMOTE_ADDR') : array('REMOTE_ADDR') as $h) {
    304335            if (!empty($_SERVER[$h])) {
    … …  
    513544    if (file_exists(__DIR__ . '/hub-install-selfhost.php')) {
    514545        require_once __DIR__ . '/hub-install-selfhost.php';
     546    require_once __DIR__ . '/hub-telemetry.php'; // opt-in "Help improve DevDome" usage reports, core 1.7.12
    515547    }
    516548}
  • devdome-malware-scanner/trunk/lib/devdome-core/hub-account.php

    r3709138 r3734917  
    1515 * identity options are routed to the main site through option filters, which covers every
    1616 * plugin's own get_option() calls without touching them. Subdomain multisite blogs keep their own
    17  * host and their own identity. Connection state stays per blog: each blog presses Connect once
    18  * and the server treats it as the same site. Core 1.7.0.
     17 * host and their own identity, and so does a subdirectory blog mapped to its own domain (core 1.7.11).
     18 * Connection state stays per blog: each blog presses Connect once and the server treats it as the
     19 * same site. Core 1.7.0.
    1920 */
    2021if (!function_exists('devdcorev1_shared_identity')) {
    2122    function devdcorev1_shared_identity()
    2223    {
    23         // Three cheap calls, evaluated each time: switch_to_blog() can change the answer within one request.
    24         return function_exists('is_multisite') && is_multisite()
     24        // Cheap calls, evaluated each time: switch_to_blog() can change the answer within one request.
     25        if (!(function_exists('is_multisite') && is_multisite()
    2526            && function_exists('is_subdomain_install') && !is_subdomain_install()
    26             && function_exists('is_main_site') && !is_main_site();
     27            && function_exists('is_main_site') && !is_main_site())) {
     28            return false;
     29        }
     30        // Core 1.7.11: identity = the DOMAIN, so a subsite MAPPED to its own domain is its own site. It shares
     31        // the network's token + Site ID only while its host is still the network's host (Codex, Analytics 1.1.5).
     32        $blog_host = strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST));
     33        $net_host  = function_exists('network_home_url') ? strtolower((string) wp_parse_url(network_home_url('/'), PHP_URL_HOST)) : '';
     34        return '' !== $blog_host && $blog_host === $net_host;
    2735    }
    2836    /** pre_option_<identity option>: the main site's value, read once per request. */
  • devdome-malware-scanner/trunk/lib/devdome-core/loader.php

    r3723150 r3734917  
    5353}
    5454
    55 DEVDCOREV1_Loader::register('1.7.10', __DIR__ . '/devdome-core.php');
     55DEVDCOREV1_Loader::register('1.7.12', __DIR__ . '/devdome-core.php');
  • devdome-malware-scanner/trunk/lib/devdome-core/uninstall.php

    r3709138 r3734917  
    4444        // The connection itself (core 1.6.1): identifiers, the site token, the cached account
    4545        // and connection state, and every short-lived connect/beacon transient.
    46         foreach (array('devdcorev1_site_id', 'devdcorev1_site_token', 'devdcorev1_account_id', 'devdcorev1_account_email', 'devdcorev1_account', 'devdcorev1_conn_state', 'devdcorev1_conn_checked', 'devdcorev1_hub_catalog') as $opt) {
     46        foreach (array('devdcorev1_telemetry', 'devdcorev1_telemetry_salt', 'devdcorev1_site_id', 'devdcorev1_site_token', 'devdcorev1_account_id', 'devdcorev1_account_email', 'devdcorev1_account', 'devdcorev1_conn_state', 'devdcorev1_conn_checked', 'devdcorev1_hub_catalog') as $opt) {
    4747            delete_option($opt);
    4848        }
  • devdome-malware-scanner/trunk/readme.txt

    r3723150 r3734917  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.3.9
     7Stable tag: 1.3.10
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    1601607. Scanner reports (devdome.com), only when you click "Report false alarm to DevDome" in the Actions menu of a finding list or "Send scan report to DevDome" on the Overview. The dialog shows the exact text before you send it: your site address, the plugin, WordPress and PHP versions, the signature version, and for each finding its severity, type, title, site-relative path, status, file hash, the plugin it belongs to and the stored evidence lines (secrets redacted, absolute paths removed), plus the note and reply e-mail you type (both optional). Never file contents. The report goes to DevDome support, who use it to fix false alarms and improve detection in the daily signature update. Terms: https://devdome.com/terms-of-service Privacy: https://devdome.com/privacy-policy
    161161
    162 8. Error reports (devdome.com), only when you press "Report this error" on an error message: the plugin sends the error text, the plugin, WordPress and PHP versions, the screen you were on, the last 20 scanner log lines with secrets removed, your site address and your admin e-mail (so support can reply) to `https://devdome.com/api/plugin/error-report`. Nothing is sent unless you press the button. Terms: https://devdome.com/terms-of-service Privacy: https://devdome.com/privacy-policy
     1628. Error reports (devdome.com), only when you press "Report this error" on an error message or "Send report" in the Report a bug dialog opened from the Overview pill (there your typed note is the error text): the plugin sends the error text, the plugin, WordPress and PHP versions, the screen you were on, the last 20 scanner log lines with secrets removed, your site address and your admin e-mail (so support can reply) to `https://devdome.com/api/plugin/error-report`. Nothing is sent unless you press the button. Terms: https://devdome.com/terms-of-service Privacy: https://devdome.com/privacy-policy
    163163
    164164The scanner does not require a DevDome account. WordPress.org verification services may still be contacted as described above.
    … …  
    264264
    265265== Changelog ==
     266
     267= 1.3.10 =
     268
     269* Overview: a small "Found a bug? Report it, we fix it within 24h" pill shows until the first scan completes or you dismiss it. It opens a note box and sends the note through the existing Report this error path (see External services, item 8); nothing is sent without your click.
     270* Shared DevDome library 1.7.12: a Cloudflare visitor address header is trusted only when the connection itself comes from a Cloudflare address, so a forged header from anywhere else is ignored.
    266271
    267272= 1.3.9 =
  • devdome-malware-scanner/trunk/uninstall.php

    r3700804 r3734917  
    3232        'devdmalw_repair_lock', 'devdmalw_sig_gen', 'devdmalw_sig_sync_hold',
    3333        'devdmalw_sig_sync_lock', 'devdmalw_start_lock', 'devdmalw_lock', 'devdmalw_fs_current',
    34         'devdmalw_paused',
     34        'devdmalw_paused', 'devdmalw_bug_pill_off',
    3535    ) as $opt) {
    3636        delete_option($opt);
Note: See TracChangeset for help on using the changeset viewer.