Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions admin/css/webdecoy-admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -1022,18 +1022,21 @@
white-space: nowrap;
}

.webdecoy-digest-status {
.webdecoy-digest-status,
.webdecoy-alerts-status {
display: flex;
align-items: center;
gap: 6px;
color: #3c4858;
margin: 16px 0 0;
}

.webdecoy-digest-status .dashicons {
.webdecoy-digest-status .dashicons,
.webdecoy-alerts-status .dashicons {
font-size: 18px;
width: 18px;
height: 18px;
flex-shrink: 0;
}

.webdecoy-connected-actions {
Expand Down
26 changes: 25 additions & 1 deletion admin/partials/settings-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@
$wd_plan_slug = isset($options['plan']) ? (string) $options['plan'] : '';
$wd_plan_label = WebDecoy_Cloud_Connect::plan_label($wd_plan_slug);
$wd_digest_on = !empty($wd_entitlements['digest']['enabled']);
$wd_alerts_on = !empty($wd_entitlements['features']['alerts']);
?>
<div class="webdecoy-connected-card">
<div class="webdecoy-connected-head">
Expand All @@ -826,6 +827,25 @@
?>
</p>

<?php
// Real-time alerts are configured in the dashboard, not here: the
// plugin sends nothing itself, which keeps the entitlement gating a
// cloud response rather than local behaviour. This row exists so a
// paying site can find the feature, and an unpaid one can see what
// it would get, without either having to guess.
?>
<p class="webdecoy-alerts-status">
<span class="dashicons <?php echo esc_attr($wd_alerts_on ? 'dashicons-bell' : 'dashicons-lock'); ?>"></span>
<?php if ($wd_alerts_on) : ?>
<?php esc_html_e('Slack and webhook alerts: On.', 'webdecoy'); ?>
<a href="https://app.webdecoy.com/enforcement/actions" target="_blank" rel="noopener">
<?php esc_html_e('Configure them in your dashboard', 'webdecoy'); ?>
</a>
<?php else : ?>
<?php esc_html_e('Slack and webhook alerts: available on Pro. Detection, blocking and the monthly report stay free.', 'webdecoy'); ?>
<?php endif; ?>
</p>

<p class="webdecoy-connected-actions">
<a href="<?php echo esc_url(WebDecoy_Cloud_Connect::wp_upgrade_url('wp_pro')); ?>" class="button button-primary" target="_blank" rel="noopener">
<?php esc_html_e('Upgrade', 'webdecoy'); ?>
Expand Down Expand Up @@ -856,7 +876,11 @@
<li><?php esc_html_e('VPN, proxy, and Tor exit node detection', 'webdecoy'); ?></li>
<li><?php esc_html_e('Cross-site threat intelligence from all WebDecoy users', 'webdecoy'); ?></li>
<li><?php esc_html_e('Advanced cloud analytics with indefinite data retention', 'webdecoy'); ?></li>
<li><?php esc_html_e('Webhook and email alert automation', 'webdecoy'); ?></li>
<?php // Not "email alerts": per-detection email is refused by the API
// (#702) because decoys are public bait and a busy site records
// thousands of hits a day. Email is the digest; webhooks are the
// real-time channel, configured in the dashboard. ?>
<li><?php esc_html_e('Webhook automation and a monthly email report', 'webdecoy'); ?></li>
</ul>
<p>
<a href="https://webdecoy.com/pricing" class="webdecoy-text-link" target="_blank" rel="noopener">
Expand Down
7 changes: 6 additions & 1 deletion includes/class-webdecoy-critical-moment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
8 changes: 4 additions & 4 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 =

Expand Down Expand Up @@ -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? =

Expand Down
40 changes: 40 additions & 0 deletions tests/CriticalMomentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}
});
Loading