@@ -856,7 +876,11 @@
-
+
+
diff --git a/includes/class-webdecoy-critical-moment.php b/includes/class-webdecoy-critical-moment.php
index bbeb97c..928fd6e 100644
--- a/includes/class-webdecoy-critical-moment.php
+++ b/includes/class-webdecoy-critical-moment.php
@@ -217,7 +217,12 @@ private function build_notice(string $ip): array
case 'unconnected':
default:
return [
- 'message' => __('A CRITICAL deception trap was just tripped. Connect to WebDecoy Cloud to see whether this attacker is already known across the network — and to block threats like it automatically.', 'webdecoy'),
+ // Was "and to block threats like it automatically". Connecting does
+ // not block anything: the cross-site feed is advisory on every plan
+ // and writes nothing to the block list (#476). The unconnected pitch
+ // was the last place in this file still promising it, three variants
+ // below a comment forbidding exactly that claim.
+ 'message' => __('A CRITICAL deception trap was just tripped. Connect to WebDecoy Cloud to see whether this attacker is already known across the network, and what it has been doing to other sites.', 'webdecoy'),
'cta_label' => __('Connect to WebDecoy Cloud', 'webdecoy'),
'cta_url' => admin_url('admin.php?page=webdecoy&tab=cloud'),
'type' => 'warning',
diff --git a/readme.txt b/readme.txt
index 97e4729..7bdd2d1 100644
--- a/readme.txt
+++ b/readme.txt
@@ -148,16 +148,16 @@ And because **monitor mode is the default**, baking WebDecoy into your boilerpla
Connect an API key to unlock cloud-powered intelligence:
-* **WAF Integrations**: arm your existing edge. Push confirmed attackers to Cloudflare or AWS WAF so they're blocked before a request ever reaches WordPress, plus webhooks for any other firewall
+* **WAF Integrations**: arm your existing edge. From your WebDecoy dashboard, push confirmed attackers to Cloudflare or AWS WAF so they're blocked before a request ever reaches WordPress, plus webhooks for any other firewall
* **IP Reputation**: AbuseIPDB integration, threat scoring
* **VPN/Proxy Detection**: identify visitors hiding behind VPNs, proxies, and Tor
* **GeoIP Enrichment**: geographic data from MaxMind
* **Cloud Sync**: forward detections to a centralized dashboard
* **Cross-Site Intelligence**: aggregate threat data from all WebDecoy customers
* **Advanced Analytics**: cloud dashboard at app.webdecoy.com with indefinite history
-* **Webhooks & Alerts**: automated response chains, email notifications
+* **Webhooks & Alerts**: automated response chains, plus a monthly email report of what was caught
-[Explore Plans](https://webdecoy.com/pricing) | [Start Free Trial](https://app.webdecoy.com/register)
+[Explore Plans](https://webdecoy.com/pricing) | [Create a free account](https://app.webdecoy.com/register)
= Threat Scoring =
@@ -212,7 +212,7 @@ Firewalls match requests against known-bad signatures, and scanners look for inf
= What does WebDecoy Cloud add? =
-WebDecoy Cloud adds threat intelligence feeds (AbuseIPDB, VPNAPI, MaxMind), centralized monitoring across multiple sites, indefinite detection history, automated response features like webhooks and email alerts, and WAF integrations: confirmed attackers can be pushed to Cloudflare or AWS WAF so they're blocked at the edge, before ever reaching your server. [Compare plans](https://webdecoy.com/pricing).
+WebDecoy Cloud adds threat intelligence feeds (AbuseIPDB, VPNAPI, MaxMind), centralized monitoring across multiple sites, indefinite detection history, a monthly email report, webhook automation, and WAF integrations: from your dashboard, confirmed attackers can be pushed to Cloudflare or AWS WAF so they're blocked at the edge, before ever reaching your server. [Compare plans](https://webdecoy.com/pricing).
= Does WebDecoy slow down my site? =
diff --git a/tests/CriticalMomentTest.php b/tests/CriticalMomentTest.php
index a835a11..827e310 100644
--- a/tests/CriticalMomentTest.php
+++ b/tests/CriticalMomentTest.php
@@ -56,3 +56,43 @@
$same('connected_upgrade', WebDecoy_Critical_Moment::variant(true, true, false), 'known + no feed -> upgrade pitch');
$same('connected_covered', WebDecoy_Critical_Moment::variant(true, true, true), 'known + feed -> confirmation copy');
});
+
+echo "\nCritical Moment: the copy must not promise a block\n";
+
+/**
+ * The four message variants are built inside a method that calls WordPress, so
+ * they cannot be invoked here. Their text can still be read.
+ *
+ * That is worth doing because this exact claim has now been removed from this
+ * file twice. #476 measured the cross-site feed and withdrew blocking from it:
+ * 0.04% of addresses were ever seen at a second site, 82% of feed entries were
+ * already a week stale, and none were still active. The connected variants were
+ * corrected then, with a comment above them saying not to claim it. The
+ * unconnected variant kept promising "to block threats like it automatically"
+ * for another three weeks, four lines below that comment.
+ *
+ * A comment asking the next person not to do something is not a guard. This is.
+ */
+$t('no translatable string in the file offers to block anything', function () use ($true) {
+ $src = file_get_contents(dirname(__DIR__) . '/includes/class-webdecoy-critical-moment.php');
+ $true($src !== false, 'source is readable');
+
+ // Only the translated strings: comments in this file discuss blocking at
+ // length, and must be free to keep doing so.
+ preg_match_all("/(?:__|_e|esc_html__|esc_html_e)\(\s*'((?:[^'\\\\]|\\\\.)*)'/", $src, $singles);
+ preg_match_all("/_n\(\s*'((?:[^'\\\\]|\\\\.)*)'\s*,\s*'((?:[^'\\\\]|\\\\.)*)'/", $src, $plurals);
+
+ $strings = array_merge($singles[1], $plurals[1], $plurals[2]);
+ $true(count($strings) >= 4, 'found the message strings (' . count($strings) . ')');
+
+ foreach ($strings as $text) {
+ $lower = strtolower($text);
+ foreach (['block', 'prevent', 'stop them'] as $promise) {
+ $true(
+ strpos($lower, $promise) === false,
+ 'copy promises "' . $promise . '" — the feed is advisory on every plan and '
+ . 'writes nothing to the block list (#476): ' . $text
+ );
+ }
+ }
+});