Security review#838
Conversation
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (37.50%) is below the target coverage (70.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## dev #838 +/- ##
============================================
- Coverage 27.58% 27.57% -0.01%
- Complexity 6052 6056 +4
============================================
Files 282 282
Lines 25180 25184 +4
============================================
- Hits 6946 6945 -1
- Misses 18234 18239 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR hardens security around remote-call execution and front-end modal rendering by restricting sensitive remote actions to verified CleanTalk RC servers and reducing XSS risk when inserting remotely loaded modal HTML.
Changes:
- Add a centralized CleanTalk RC servers allowlist and require verified RC origin for plugin lifecycle remote-call actions.
- Sanitize modal HTML before assigning to
innerHTML, including when linkifying URLs. - Propagate the modal sanitization changes into prebuilt and minified bundle artifacts.
Reviewed changes
Copilot reviewed 10 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/Cleantalk/ApbctWP/RemoteCalls.php | Adds RC server allowlist and enforces verified-origin checks for sensitive plugin lifecycle remote calls. |
| js/src/common-cleantalk-modal.js | Sanitizes loaded modal HTML before injecting into the DOM. |
| js/prebuild/apbct-public-bundle.js | Prebuilt bundle updated to include modal HTML sanitization. |
| js/prebuild/apbct-public-bundle_int-protection.js | Prebuilt bundle variant updated to include modal HTML sanitization. |
| js/prebuild/apbct-public-bundle_int-protection_gathering.js | Prebuilt bundle variant updated to include modal HTML sanitization. |
| js/prebuild/apbct-public-bundle_gathering.js | Prebuilt bundle variant updated to include modal HTML sanitization. |
| js/prebuild/apbct-public-bundle_full-protection.js | Prebuilt bundle variant updated to include modal HTML sanitization. |
| js/prebuild/apbct-public-bundle_full-protection_gathering.js | Prebuilt bundle variant updated to include modal HTML sanitization. |
| js/prebuild/apbct-public-bundle_ext-protection.js | Prebuilt bundle variant updated to include modal HTML sanitization. |
| js/prebuild/apbct-public-bundle_ext-protection_gathering.js | Prebuilt bundle variant updated to include modal HTML sanitization. |
| js/common-cleantalk-modal.min.js.map | Source map updated to reflect modal sanitization changes. |
| js/common-cleantalk-modal.min.js | Minified modal script updated to include modal HTML sanitization. |
| js/apbct-public-bundle.min.js | Minified public bundle updated to include modal HTML sanitization. |
Comments suppressed due to low confidence (1)
lib/Cleantalk/ApbctWP/RemoteCalls.php:134
- Same strict hostname comparison issue as in
checkWithoutToken(): consider normalizing the verified hostname (lowercase + trim trailing '.') before checking againstRC_SERVERSto prevent accidental lockouts if DNS returns different casing or a trailing dot.
if ( in_array($action, self::$pluginLifecycleActions, true) ) {
// Resolve the client IP to a hostname (reverse DNS) and check it against the RC servers allowlist.
$client_ip = Helper::ipGet('remote_addr');
$verified_hostname = $client_ip ? \Cleantalk\Common\Helper::ipResolve($client_ip) : false;
if ( $verified_hostname === false || ! in_array($verified_hostname, self::RC_SERVERS, true) ) {
die('FAIL ' . json_encode(array('error' => 'FORBIDDEN_SOURCE')));
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Resolve IP of the client making the request and verify hostname from it to be in the list of RC servers hostnames | ||
| $client_ip = Helper::ipGet('remote_addr'); | ||
| $verified_hostname = $client_ip ? \Cleantalk\Common\Helper::ipResolve($client_ip) : false; | ||
| $is_noc_request = ! $apbct->key_is_ok && | ||
| Request::get('spbc_remote_call_action') && | ||
| in_array(Request::get('plugin_name'), array('antispam', 'anti-spam', 'apbct')) && | ||
| $verified_hostname !== false && | ||
| in_array($verified_hostname, $rc_servers, true); | ||
| in_array($verified_hostname, self::RC_SERVERS, true); |
| content.innerHTML = this.sanitizeHtml(this.loaded); | ||
| } else { | ||
| content.innerHTML = this.loaded.replace(urlRegex, '<a href="$1" target="_blank">$1</a>'); | ||
| content.innerHTML = this.sanitizeHtml(this.loaded.replace(urlRegex, '<a href="$1" target="_blank">$1</a>')); |
| let forbiddenTags = ['SCRIPT', 'IFRAME', 'OBJECT', 'EMBED', 'BASE', 'LINK', 'META']; | ||
|
|
||
| template.content.querySelectorAll( '*' ).forEach( function( el ) { | ||
| if ( forbiddenTags.indexOf( el.tagName ) !== -1 ) { | ||
| el.remove(); | ||
| return; | ||
| } |
https://app.doboard.com/1/task/52113