Wednesday, October 28, 2026

Fixing Broken Blogger Telemetry: Upgrading to GA4, Cross-Domain Linking, and Excluding Admin Visits

If you've been running a legacy technical blog on Google Blogger, chances are your traffic telemetry is either dead or wildly distorted. Older Blogger templates relied on Universal Analytics (UA-XXXXX-Y), which stopped recording hits when Google permanently sunsetted the platform. Additionally, Blogger’s native "Stats" tab counts every scraping bot, RSS scanner, and search crawler as a legitimate human visit.

Here is an end-to-end guide on migrating your Blogger instance to modern Google Analytics 4 (GA4), configuring multi-domain tracking if you run a separate business or portfolio site, filtering out your own admin pageviews, and injecting clean, XML-safe templates.


1. Provisioning a Modern GA4 Measurement ID

GA4 properties identify websites via a Measurement ID starting with a G- prefix instead of the obsolete UA- tags.

  1. Navigate to Google Analytics.
  2. In the bottom-left corner, click Admin (gear icon).
  3. Under Data collection and modification, select Data streams.
  4. Click into your Web stream. In the top-right corner of the Web stream details pane, locate and copy your Measurement ID (e.g., G-DN6TEYJ52G).

2. Deploying the Tag to the Blogger Dashboard

Blogger features a native integration field that injects the required gtag.js headers across your entire site structure without manually modifying XML headers:

  1. Open your Blogger Dashboard.
  2. From the left-hand navigation rail, click Settings → Basic.
  3. Locate Google Analytics Measurement ID.
  4. Clear out any expired UA- code, paste your new G-XXXXXXXXXX tag, and click Save.

3. Configuring Cross-Domain Measurement

If your blog serves as a funnel to your primary company website, agency, or portfolio, sending users from your blog to your main domain typically splits the session into two disconnected visits. Cross-domain measurement keeps attribution intact:

  1. In GA4, return to Data streams → click your web stream → click Configure tag settings.
  2. Under the Settings card, click Configure your domains.
  3. Click Add condition:
    • Match type: Contains
    • Domain: Your primary site domain (e.g., yourcompany.com)
  4. Click Add condition again:
    • Match type: Contains
    • Domain: Your blog domain (e.g., yourblog.com)
  5. Click Save. GA4 will now automatically append linking query parameters across cross-domain anchor tags, preserving initial session origin, referrer sources, and conversion funnels.

4. Eliminating Self-Traffic & Bringing Filters Out of Testing

Frequent editing and testing will corrupt your metrics if your internal views aren't excluded. You should drop self-traffic at both the GA4 level and Blogger level:

A. GA4 Internal IP Exclusion
  1. In GA4 Admin, navigate to Data streams → Configure tag settings → Show all → Define internal traffic.
  2. Click Create, assign a name (e.g., Office IP), set Match type to IP address equals, and enter your public WAN IP.
Crucial Step: Taking Filters Out of Testing Mode

By default, GA4 creates the Internal Traffic data filter in Testing mode. In this mode, GA4 evaluates the traffic and flags it with a dimension, but does not actually exclude it from your metrics. You must manually activate it:

  1. Return to the Admin panel under Data collection and modification → Data filters.
  2. Click the Internal Traffic row.
  3. Scroll down to Filter state, select Active, and click Save.
  4. Confirm by clicking Activate filter. Only now will your internal visits be permanently filtered from live analytics data.
B. Blogger Dashboard Native Blocker

In the Blogger left sidebar, click Stats → scroll to the very bottom → click Manage tracking your own pageviews → check "Don't track my views for this blog". This drops a client-side tracking cookie in your browser to stop Blogger's internal counter from registering your session.

5. XML-Safe In-Article Conversion Cards & Obfuscation

When adding conversion elements or lead capture boxes under individual blog posts, standard HTML can cause compilation errors inside Blogger's XML parser. Common pitfalls include:

  • Reserved Entities: Characters like right arrows (→) will crash the XML compiler with a SAXParseException. Always use numeric entities (→) or raw UTF-8 characters (→).
  • CDATA Block Wrapping: Any JavaScript containing logical operators (like && or <) must be encapsulated within //<![CDATA[ ... //]]> blocks.
  • Scraper Obfuscation: Avoid hardcoding raw phone numbers or email addresses in post bodies. Storing contact points in Base64 and unmasking them via DOM replacement on user interaction shields you from scraping crawlers:
<!-- Lightweight Obfuscated Reveal Component -->
<span style="font-size: 13px;">Direct Support: 
  <strong id="lead-ph" style="cursor: pointer; color: #0284c7; text-decoration: underline;" 
          onclick="revLead(this)" onmouseover="revLead(this)">
    Reveal Contact Line
  </strong>
</span>

<script type="text/javascript">
//<![CDATA[
function revLead(el) {
  if (!el || el.getAttribute('data-unmasked') === '1') return;
  try {
    // Encoded '555-555-0199' in Base64
    var p = atob('NTU1LTU1NS0wMTk5');
    var a = document.createElement('a');
    a.href = 'tel:' + p.replace(/-/g, '');
    a.textContent = p;
    a.style.color = '#0284c7';
    a.style.fontWeight = '700';
    a.style.textDecoration = 'none';
    el.setAttribute('data-unmasked', '1');
    el.parentNode.replaceChild(a, el);
  } catch(e) {}
}
//]]>
</script>

6. Testing Realtime Telemetry

Once configured, verify transmission:

  1. Open a clean Incognito or Private browsing window (ensuring it's outside your filtered IP or on a mobile carrier connection).
  2. Load an article on your blog.
  3. In your Google Analytics panel, select Reports → Realtime.
  4. Within 30 seconds, you will see a live session record in the geographic distribution map alongside automatic telemetry for user engagement, scroll depth, and outbound link clicks.

Tuesday, October 27, 2026

Defending osTicket and WordPress: Incident Response and Automated Blocking with CrowdSec

If you run internet-facing web applications like osTicket and WordPress, automated credential-stuffing bots will eventually find your login endpoints. To stop bursts of "Excessive login attempts" alerts hitting a login portal (/scp/login.php), investigate the server logs, confirm zero breaches occurred, and implement a multi-layered defense using UFW and CrowdSec.

Here is the full incident breakdown and the exact steps to implement this automated security setup on an Ubuntu Linux server.


1. Immediate Mitigation via Linux Firewall

When dealing with an active brute-force flood from a persistent IP address, the fastest remediation is to drop all incoming packets at the firewall before the web server expends resources processing the requests:

sudo ufw insert 1 deny from 66.232.121.113 to any
sudo ufw reload

Why insert 1 matters: Firewalls evaluate rules sequentially from top to bottom and exit on the first match. Using insert 1 places the rule at the very top of the rule chain, ensuring the traffic is dropped before any broader allow 80/tcp or allow 443/tcp web rules can accept it.


2. Post-Incident Forensics: Confirming Zero Breach

Before moving forward, verify whether the attacking IP has compromised the system by inspecting the Apache access logs and system authentication logs:

sudo grep -rn '66.232.121.113' /var/log/apache2/
sudo grep '66.232.121.113' /var/log/auth.log

The logs can confirm the following:

  • Repeated Login Loops: Every single request from the IP was exclusively cycling between GET /scp/login.php and POST /scp/login.php.
  • No Dashboard Traversal: If an authentication attempt had succeeded, osTicket would have issued a redirect to an internal page (such as /scp/index.php or /scp/tickets.php) followed by static resource requests. No internal endpoints were ever requested.
  • No Shell Access: /var/log/auth.log showed zero inbound SSH connections, confirming no external access to the underlying OS.

3. Long-Term Automated Defense with CrowdSec

Manually blocking individual IP addresses does not scale. To automate threat mitigation across osTicket, WordPress, and SSH, we installed CrowdSec along with its iptables firewall remediation bouncer.

Step A: Install CrowdSec & the Firewall Bouncer

Add the official repository and install the engine and bouncer:

# 1. Add repository and install security engine
curl -s https://install.crowdsec.net | sudo sh
sudo apt update
sudo apt install -y crowdsec

# 2. Install the iptables/firewall remediation component
sudo apt install -y crowdsec-firewall-bouncer-iptables

Step B: Enable WordPress Threat Scenarios

To guard against WordPress user enumeration, credential attacks on wp-login.php, and wp-config.php probes, install the official collection:

sudo cscli collections install crowdsecurity/wordpress
sudo systemctl reload crowdsec

Step C: Create a Custom osTicket Brute-Force Scenario

While CrowdSec includes generic HTTP protection rules, osTicket's login behavior is best served by a dedicated scenario that monitors repeated rapid POST requests targeting /login.php.

Create /etc/crowdsec/scenarios/osticket-bf.yaml:

sudo tee /etc/crowdsec/scenarios/osticket-bf.yaml > /dev/null << 'EOF'
type: leaky
name: local/osticket-bf
description: "Detect brute-force attempts on osTicket agent and client logins"
filter: "evt.Meta.log_type in ['apache2_access', 'http_access-log'] && evt.Parsed.request contains '/login.php' && evt.Parsed.verb == 'POST'"
groupby: "evt.Meta.source_ip"
capacity: 5
leakspeed: "10s"
blackhole: "5m"
labels:
  service: http
  type: bruteforce
  remediation: true
EOF

Reload the agent and verify the new scenario is registered:

sudo systemctl reload crowdsec
sudo cscli scenarios list | grep osticket

4. Operational Health & Management Commands

With this pipeline active, CrowdSec automatically monitors Apache access logs, parses incoming events, and instructs the firewall bouncer to drop malicious traffic dynamically. Useful operational commands include:

  • Check Live Parser & Bouncer Metrics: sudo cscli metrics
  • View Active Bans: sudo cscli decisions list
  • View Triggered Alerts: sudo cscli alerts list
  • Manually Ban an IP: sudo cscli decisions add --ip <IP> --duration 720h --reason "brute force"
  • Remove a False Positive Ban: sudo cscli decisions delete --ip <IP>
cfb3339690c974a7

Wednesday, October 21, 2026

Patching the Engine: Remediating Critical PHP Flaws in Production Docker Stacks

Security Advisory & Incident Response

Remediating Critical PHP Engine Flaws in Production Docker Stacks

A step-by-step technical post-mortem: runtime verification, upstream tag validation, and container cache resolution.

1. Incident Context: The Threat Vector

On October 21, 2026, coordinated emergency advisories dropped across the web infrastructure community addressing critical unauthenticated Remote Code Execution (RCE) flaws deep within core PHP request parsing and memory management routines (CVE-2026-109618 and CVE-2026-109619). The vulnerabilities reside at the engine layer, meaning typical web application firewalls and reverse proxies forwarding raw HTTP traffic cannot reliably inspect or sanitize malicious triggers.

Standard security targets required immediate updates to safe patch levels across all active branches:

  • PHP 8.2: Upgraded to 8.2.35+
  • PHP 8.3: Upgraded to 8.3.36+
  • PHP 8.4: Upgraded to 8.4.27+

2. Reverse Proxies & Ingress Filtering

A frequent initial assumption is that placing workloads behind an edge reverse proxy (such as Traefik, Nginx, or HAProxy) provides inherent layer-7 protection. In practice, unless the proxy actively implements a deep-packet inspection WAF (like Coraza/ModSecurity) with explicit signatures, it merely terminates TLS and forwards standard payloads straight downstream to the vulnerable PHP worker pools.

During a zero-day disclosure window, edge proxies should primarily be leveraged as gatekeepers—either enforcing immediate IP allowlisting or temporarily terminating public routes while downstream container images rebuild upstream.

3. Execution Workflow: Upgrading Docker Runtimes

Below is the operational runbook executed to audit, update, and harden a production container stack without relying on blind image pull assumptions.

Step 3.1: Identifying Running Runtimes

Never rely strictly on host labels or compose filenames. Execute directly against the containerized binary to verify the compiled engine version:

# Query the running container binary
docker exec -it app-core-web-production php -v

# Vulnerable baseline detected:
# PHP 8.3.25 (cli) (built: Sep  8 2025 21:19:20) (NTS)
# Zend Engine v4.3.25, OPcache v8.3.25

Step 3.2: Upstream Image Verification & Pull

Upstream registry builds lag behind official patch declarations. Pulling an image tag before automated pipelines build and push the updated binaries merely re-pulls the vulnerable layers. Checking repository tags and testing transiently ensures the fix is present before recreating the production workload:

# Pull new base layers from registry
docker pull wordpress:php8.3-apache

# Transiently spin up and confirm the underlying patch level
docker run --rm wordpress:php8.3-apache php -v

# Confirmed safe build:
# PHP 8.3.36 (cli) (built: Oct 21 2026 15:57:18) (NTS)
# Zend Engine v4.3.36, OPcache v8.3.36

Step 3.3: Configuration Hardening & Stack Recreation

To provide defense-in-depth against upload-progress heap-grooming vectors, disable progress tracking globally inside the runtime configuration (custom-php.ini):

session.upload_progress.enabled = 0

Trigger container recreation to swap the running process over to the newly verified image layer:

# Recreate production container with forced re-bind
docker compose up -d --force-recreate

# Final verification against production runtime
docker exec -it app-core-web-production php -v

4. Troubleshooting Image Cleanup: The "In-Use" Image Trap

When attempting to purge old, superseded images (often showing as <none> or legacy build hashes), Docker daemon safety checks may prevent deletion with errors stating the image is still in use.

Key Docker Behavior: Even if a container is in a Stopped state (such as staging branches, dev sandboxes, or test instances), Docker maintains an active lock on that image layer. An image cannot be pruned until every associated container is either removed or recreated against a newer image layer.

To release image references without breaking environments:

  1. For Active/Staging Workloads: Recreate the stopped or staging stacks using the freshly pulled image. As each container binds to the new image, its reference to the legacy image drops automatically.
  2. For Deprecated Environments: Remove dormant container instances explicitly:
    docker rm sandbox-app-dev staging-web-instance
    docker image prune -f

Operational Takeaways

  • Do not assume :latest tags or container rebuilds pull patched code until build timestamps and version binaries are verified inside a transient test container.
  • Layer-7 reverse proxies do not insulate backend engines from deep runtime parsing bugs without explicit WAF rules or IP-level lockdown.
  • Stopped containers maintain active image layer locks; audit staging and sandbox workloads regularly during security patch cycles.

ec6a761bc8e895a0

Thursday, October 15, 2026

Why Your Docker Containers Can't Resolve DNS: Fixing pfSense Unbound Timeouts

Fixing Periodic Docker Webhook Drops Caused by pfSense DNS Resolver (Unbound) Freezes

A comprehensive root cause analysis and step-by-step hardening guide for containerized services behind reverse proxies.

If you run containerized uptime monitors, alert dispatchers, or reverse-proxy stacks on a local server, you might run into an issue where outgoing notifications (such as Discord or Slack webhooks) suddenly stop firing. Inspecting the container logs often reveals failures like getaddrinfo ENOTFOUND or EAI_AGAIN.

A quick restart of the pfSense DNS Resolver (Unbound) usually restores connectivity immediately, but when this happens repeatedly every week or two, the root issue lies directly within how Unbound is processing queries.

Root Cause Analysis: What the Logs Show

Analyzing live Unbound logs when resolution drops reveals two hidden bottlenecks that interact to freeze the resolver service:

1. Excessive Debug Logging (Log Level ≥ 2)

What happens: The resolver writes dozens of granular trace messages (cache memory operations, module target iteration, module state transitions) per millisecond for every outbound query.

Why it breaks things: Disk and syslog I/O cannot keep up with high-frequency network queries. Unbound's single-threaded event loop blocks on write calls, causing incoming UDP DNS queries to time out and drop.

2. Iterative Root Recursion & Subquery Exhaustion

What happens: In pure resolver mode, pfSense manually walks every level of the global DNS tree (root . → TLD .com → authoritative nameservers → CNAME records) while validating multi-tiered DNSSEC cryptographic trust chains.

Why it breaks things: When any authoritative intermediate nameserver experiences packet loss or delays a response, Unbound logs event:module_event_noreply. The subqueries remain waiting in memory, worker threads pile up, and the service stops answering local LAN and Docker requests entirely.

Step-by-Step Fixes and Rationale

Step 1: Lower Logging Verbosity

  • Go to Services > DNS Resolver > Advanced Settings.
  • Set Log Level to Level 0 (or Level 1).
  • Click Save and Apply Changes.

Why we do this: Eliminates continuous disk write contention and prevents syslog thread blocking. Unbound can process thousands of lookups in memory without pausing to flush trace lines to the firewall storage.

Step 2: Enable DNS Query Forwarding & Disable Redundant DNSSEC

  • Go to Services > DNS Resolver > General Settings.
  • Check Enable Forwarding Mode under DNS Query Forwarding.
  • Uncheck DNSSEC.
  • Click Save and Apply Changes.

Why we do this: Instead of pfSense querying 5 to 10 authoritative servers across the globe for every lookup, it delegates resolution to high-speed upstream anycast servers (Cloudflare, Google). These enterprise resolvers already validate DNSSEC and maintain massive global caches, drastically cutting query response times and bypassing subquery lockups completely.

Step 3: Define Upstream Resolvers and Disable ISP Overrides

  • Go to System > General Setup.
  • Add reliable upstream IPs under DNS Server Settings (e.g., 1.1.1.1 and 8.8.8.8), explicitly selecting your WAN Gateway.
  • Uncheck DNS Server Override.
  • Click Save.

Why we do this: Explicit gateway binding ensures DNS traffic always routes directly out of the active internet link. Unchecking "DNS Server Override" prevents your local ISP from pushing slower or less reliable DNS addresses during WAN DHCP lease renewals.

Step 4: Disable Dynamic DHCP Lease Registration

  • Go to Services > DNS Resolver > General Settings.
  • Scroll to the bottom and ensure DHCP Registration (Register DHCP leases in the DNS Resolver) is unchecked.
  • Leave Static DHCP checked if you map local servers statically.
  • Click Save and Apply Changes.

Why we do this: This is the most common cause of recurring Unbound crashes in pfSense. When enabled, pfSense completely reloads the Unbound service every single time a phone, laptop, or IoT device requests or renews an IP lease. On active networks, these constant restarts corrupt the cache or deadlock the process.

Step 5: Add Container-Level DNS Fallbacks

In your Docker Compose file, explicitly define upstream resolvers under the service definition:

services:
  monitoring-service:
    image: louislam/uptime-kuma:latest
    container_name: uptime-kuma
    restart: always

    # Provide direct DNS resolvers to ensure alerting never hangs on LAN DNS
    dns:
      - 1.1.1.1
      - 8.8.8.8

    networks:
      - web

Why we do this: Establishes defense-in-depth. If your internal router is ever rebooted, updated, or temporarily unreachable, your alert monitoring containers can still resolve external APIs and dispatch critical downtime notifications without interruption.

Expected Outcome: By turning off the DHCP restart loop, offloading recursive lookups to fast upstream forwarders, and removing excessive disk logging, Unbound maintains a lean in-memory cache and stops locking up. Internal hosts and containers will consistently resolve names without requiring manual firewall intervention.

Thursday, August 27, 2026

The Missing Tenant Setting: How to Set Mailbox Time Zones in Bulk for M365

If you've ever provisioned a brand-new Microsoft 365 tenant or created a batch of new user mailboxes, you might have noticed something strange in Outlook on the Web or the new Outlook desktop client: email timestamps and calendar entries are off by 4 or 5 hours.

By default, newly provisioned Exchange Online mailboxes inherit an unconfigured or UTC (Coordinated Universal Time) state. Because Microsoft treats regional settings as an individual mailbox attribute rather than a global tenant policy, there is no master switch in the admin portal to set a default time zone across the organization.

Why does this matter?

While legacy "Classic" Outlook desktop apps often read the local PC clock, Outlook on the Web (OWA), New Outlook for Windows/Mac, Out-of-Office auto-replies, and Calendar scheduling assistants strictly evaluate against the server-side mailbox time zone.


Method 1: Batch-Fix All Mailboxes via PowerShell (Recommended)

The Exchange Admin Center (EAC) web GUI lacks regional configuration settings. Using the Exchange Online PowerShell module is the fastest way to align every user, shared mailbox, and resource at once.

Step 1: Connect to Exchange Online

Connect-ExchangeOnline

Step 2: Apply Time Zone & Language Across the Tenant

For All Standard User Mailboxes:

Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox | Set-MailboxRegionalConfiguration -TimeZone "Eastern Standard Time" -Language "en-US"

For Shared Mailboxes & Room Resources:

Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails SharedMailbox,RoomMailbox | Set-MailboxRegionalConfiguration -TimeZone "Eastern Standard Time" -Language "en-US"

For a Single Specific User:

Set-MailboxRegionalConfiguration -Identity "user@yourdomain.com" -TimeZone "Eastern Standard Time" -Language "en-US"

Step 3: Verify the Changes

Get-MailboxRegionalConfiguration -Identity "user@yourdomain.com" | Select-Object Identity, TimeZone, Language, DateFormat, TimeFormat

Method 2: Manual End-User Setup via Outlook Web App (OWA)

If an end user needs to adjust their own time zone manually without administrator intervention:

  1. Log into Outlook on the Web.
  2. Click the Gear icon (Settings) located in the top-right header bar.
  3. Navigate to General > Language and time.
  4. Under Time zone, select your local region (e.g., (UTC-05:00) Eastern Time (US & Canada)).
  5. Click Save.

Supported Microsoft TimeZone IDs Reference

Exchange Online requires specific string identifiers for the -TimeZone parameter. Even though these identifier strings contain the word "Standard Time", Exchange automatically adjusts for Daylight Saving Time dynamically.

To list all valid system time zone IDs directly inside your active PowerShell session:

[System.TimeZoneInfo]::GetSystemTimeZones() | Select-Object Id, DisplayName | Format-Table -AutoSize

Common US & North American Time Zones

Time Zone PowerShell -TimeZone Value Standard UTC Offset
Eastern Time "Eastern Standard Time" UTC-05:00
Central Time "Central Standard Time" UTC-06:00
Mountain Time "Mountain Standard Time" UTC-07:00
Mountain (Arizona / No DST) "US Mountain Standard Time" UTC-07:00
Pacific Time "Pacific Standard Time" UTC-08:00
Alaska "Alaskan Standard Time" UTC-09:00
Hawaii "Hawaiian Standard Time" UTC-10:00
Atlantic (Canada) "Atlantic Standard Time" UTC-04:00
Newfoundland "Newfoundland Standard Time" UTC-03:30

Common International Time Zones

Region / Hub PowerShell -TimeZone Value Standard UTC Offset
UTC Default "UTC" UTC+00:00
London / GMT "GMT Standard Time" UTC+00:00
Western Europe (Paris, Berlin) "W. Europe Standard Time" UTC+01:00
Eastern Europe (Athens, Kyiv) "FLE Standard Time" UTC+02:00
Dubai / UAE "Arabian Standard Time" UTC+04:00
India "India Standard Time" UTC+05:30
Singapore / Beijing "Singapore Standard Time" UTC+08:00
Tokyo "Tokyo Standard Time" UTC+09:00
Sydney / Melbourne "AUS Eastern Standard Time" UTC+10:00
Auckland / New Zealand "New Zealand Standard Time" UTC+12:00

MSP Best Practice: Automate Tenant Onboarding

Since Microsoft 365 does not automatically inherit a tenant-wide default time zone for future accounts, make it a standard SOP to append the Set-MailboxRegionalConfiguration command directly into your new-hire or new-tenant provisioning scripts to avoid post-deployment support tickets.

GGcac119f3f2508aa3

Thursday, August 20, 2026

WordPress - Fixing the WP Activity (plugin) Log Fatal TypeError on PHP 8.x (class-wp-helper.php)

Fixing the WP Activity Log Fatal TypeError on PHP 8.x (class-wp-helper.php)

If you recently upgraded your WordPress environment to PHP 8.0, 8.2, or 8.3 and noticed a "There has been a critical error on this website" screen when loading the WP Activity Log viewer (/wp-admin/admin.php?page=wsal-auditlog), you are likely running into an unhandled scalar type mismatch in modern PHP runtimes.

Tracked on the WordPress Support Forums: Fatal error 5.6.5 with WP 7.1: strtolower receives integer callback id (View Forum Hotfix Reply)


The Error Breakdown

Checking the site's debug.log file reveals a fatal TypeError originating from the plugin's notice-suppression helper:

[20-Aug-2026 05:28:54 UTC] PHP Fatal error: Uncaught TypeError: strtolower(): Argument #1 ($string) must be of type string, int given in /var/www/html/wp-content/plugins/wp-security-audit-log/classes/Helpers/class-wp-helper.php:656
Stack trace:
#0 /var/www/html/wp-content/plugins/wp-security-audit-log/classes/Helpers/class-wp-helper.php(656): strtolower(1243)
#1 /var/www/html/wp-content/plugins/wp-security-audit-log/classes/Helpers/class-wp-helper.php(537): WSAL\Helpers\WP_Helper::remove_unrelated_actions('admin_notices')
#2 /var/www/html/wp-includes/class-wp-hook.php(353): WSAL\Helpers\WP_Helper::hide_unrelated_notices('')
#3 /var/www/html/wp-includes/class-wp-hook.php(377): WP_Hook->apply_filters(NULL, Array)
#4 /var/www/html/wp-includes/plugin.php(523): WP_Hook->do_action(Array)
#5 /var/www/html/wp-admin/admin-header.php(151): do_action('admin_print_scr...')
#6 /var/www/html/wp-admin/admin.php(244): require_once('/var/www/html/w...')
#7 {main}
  thrown in /var/www/html/wp-content/plugins/wp-security-audit-log/classes/Helpers/class-wp-helper.php on line 656

Why It Happens

To keep the activity log viewer interface clean, the plugin executes hide_unrelated_notices() on the admin_notices action hook. It scans registered callback functions and runs strtolower() against each callback identifier to determine whether the notice belongs to WP Activity Log or another plugin.

In PHP 7.x, passing non-string values (such as integer identifiers generated by dynamic hooks, anonymous functions, or closures) into strtolower() resulted in silent typecasting. In PHP 8.0+, scalar type checking is strictly enforced: passing an integer like 1242 or 1243 throws an uncaught TypeError and immediately crashes script execution.


The Workaround / Hotfix

Until an upstream update is released by the plugin developers, you can apply a one-line hotfix to cast the callback parameter explicitly to a string before evaluation.

Manual File Edit

Open /wp-content/plugins/wp-security-audit-log/classes/Helpers/class-wp-helper.php and navigate to line 656:

// Original line:
strtolower( $callback )

// Updated line:
strtolower( (string) $callback )

Quick Docker / Shell Command

If you manage your WordPress instance in Docker or have SSH terminal access, you can run a non-destructive in-place sed substitution:

# Inside a standard Linux server / webroot:
sed -i 's/strtolower(\([^)]*\))/strtolower((string)\1)/g' wp-content/plugins/wp-security-audit-log/classes/Helpers/class-wp-helper.php

# Or directly inside a running Docker container:
docker exec -it your-wordpress-container sed -i 's/strtolower(\([^)]*\))/strtolower((string)\1)/g' /var/www/html/wp-content/plugins/wp-security-audit-log/classes/Helpers/class-wp-helper.php

Once applied, refresh the WP Activity Log page in your admin dashboard. The activity audit log table will render immediately without triggering fatal errors.

Follow the community thread or submit further feedback on the official WordPress Support Forum topic.

GG-c1c4d41df18c3c5c

WordPress - Troubleshooting Blank Plugin Settings Pages After a Core Update

Troubleshooting Blank Plugin Settings Pages in WordPress After a Core Update

Following a recent WordPress core update to version 7.1, several plugin settings screens (such as Fluent Mail, User Role Editor, and Block Visibility) rendered completely blank in the WordPress admin dashboard. The outer administrative chrome—including the top toolbar, left sidebar, and version footer—loaded normally, but the inner content container failed to mount.

Initial Symptoms & Diagnostic Steps

  • Server-Side Execution: Enabling WP_DEBUG and checking wp-content/debug.log showed that PHP executed through to completion on page visits without throwing blocking fatal errors during the initial options page rendering.
  • Browser Console Exceptions: Opening Chrome DevTools (F12 → Console) revealed cascading JavaScript failures across core WordPress scripts:
    Uncaught SyntaxError: Invalid or unexpected token (at load-scripts.php:12)
    Uncaught SyntaxError: Invalid or unexpected token (at i18n.min.js:3)
    Uncaught ReferenceError: wp is not defined
  • Asset Inspection: Viewing core script files (such as wp-includes/js/dist/i18n.min.js) inside the DevTools Sources tab revealed lines filled entirely with null bytes (\0\0\0... / NULNULNUL).

Root Cause

During automated background core updates or uncompressed archive extractions, an interrupted write stream or volume buffer flush can cause the filesystem to pre-allocate zero-padded blocks without finalizing the script content. JavaScript parsers fail immediately upon encountering raw null bytes in source files, halting script execution before WordPress packages (like wp.i18n, wp.element, and wp.domReady) can initialize the React-based admin interfaces.

Standard in-place core reinstalls through the WordPress dashboard often write over existing file footprints without truncating trailing zero-padded blocks, leaving the corrupt null bytes in place while browsers aggressively cache the broken assets.


The Solution

To resolve the issue, the corrupted core JavaScript directories were completely cleared and replaced with clean source files from the official WordPress release archive, followed by resetting the asset cache.

1. Clean and Rebuild Core JavaScript Directories

Execute the following commands inside the container or server shell to wipe the corrupted asset folders and extract a fresh copy directly from WordPress.org:

cd /tmp
curl -sO https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
rm -rf /var/www/html/wp-includes/js/*
rm -rf /var/www/html/wp-admin/js/*
cp -rf wordpress/wp-includes/js/* /var/www/html/wp-includes/js/
cp -rf wordpress/wp-admin/js/* /var/www/html/wp-admin/js/
chown -R www-data:www-data /var/www/html/wp-includes/js /var/www/html/wp-admin/js
rm -rf latest.tar.gz wordpress

2. Clear Storage & Browser Caching

  • Server Cache: Clear any cached scripts in wp-content/cache/ if object or asset caching plugins are enabled.
  • Browser Cache: Open browser DevTools, enable Disable cache under the Network tab, and perform a hard refresh (Ctrl + Shift + R or Ctrl + F5).

Once fresh scripts are placed and the local cache is bypassed, core packages initialize cleanly, and all React and block-dependent plugin settings panels render immediately.

GG-c1c4d41df18c3c5c

Wednesday, July 22, 2026

Dahua Console for Remote Access to NVR/IPCs



WSL --> Debian --> Navigate to a specific folder for this environment.


Clone the repo:

    git clone https://github.com/mcw0/DahuaConsole.git

Create the virtual environment for the python script:

    cd DahuaConsole

    python3 -m venv venv
    source venv/bin/activate

Install the package's requirements:

    pip install -r requirements.txt


Help command for script:

    python3 Console.py -h

Remote connect to a machine or IPC camera:

    python Console.py --rhost 10.1.10.200 --proto dvrip --rport 37777 --auth userName:password


*Reboot command exists and can be helpful when the webui stops responding.




πŸ‘½

Monday, July 13, 2026

pfSense Useful SSH Commands

 


Get user creation dates:

bzgrep "Successfully created user" /var/log/system.log* | awk '{print $1, $2, $3, "-> User Created:", $NF}' | uniq


Get OpenVPN connections per user (newest at bottom):

bzgrep -E "user '.*' authenticated" /var/log/openvpn.log*



Wednesday, May 13, 2026

Solving Asymmetric Routing: Accessing LAN Devices with Misconfigured Gateways

 

Issue:

Managed switch at 192.168.1.2 was reachable via ARP but "filtered" on all ports during Nmap scans over OpenVPN. The switch had a misconfigured Default Gateway (.254 instead of .1.1), causing a routing dead-end where the switch could receive packets but couldn't route replies back to the 10.8.0.0/24 VPN subnet.



Temporary Resolution (VPN):

Implemented a Hybrid Outbound NAT rule in pfSense on the LAN interface. This masqueraded VPN traffic as coming from the LAN interface IP (192.168.1.1), tricking the switch into responding to a local address. Once GUI access was gained, the System Default Gateway was corrected to 192.168.1.1 and saved to flash.



Resolution:

Update switch gateway to correct gateway. In this case it was .254 to .1


πŸ‘½

Sunday, May 10, 2026

UPS WorldShip - Cannot Move Backup Data to New Machine

Issue:

Cannot move customer data from one machine to another without multiple useless errors.


Resolution:

As per UPS tech support, both previous and new application must be the same version. You must upgrade the old version in multiples of two versions at a time. Thanks UPSπŸ˜’

Download links as per TS in order to upgrade v2016 ➡️ v2026 (also keep in mind, their versioning in the file name does not match the program year):

(1) https://download.worldshipmedia.com/production/WS21_0_24_0_ENU.exe
(2) https://download.worldshipmedia.com/production/2020_wwe/WS2020_23_8.exe
(3) https://download.worldshipmedia.com/production/WS24_0_24_0_ENU.exe
(4) https://download.worldshipmedia.com/production/WS28_0_905_0_ENU.exe


LATEST as of 05.06.26:

(5) https://download.worldshipmedia.com/production/WS29_0_105_0_ENU.exe


============================================================


1. Do upgrades --> Support Tab (old machine) --> Move Data

2. Export, pack, move data to new machine, unpack, point WorldShip installer at old data on new machine.

Default Backup Data Location: C:\UPS\WSTD\Support\DBSupport




πŸ‘½