Security review - #838
Conversation
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (47.05%) 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 28.47% 28.45% -0.02%
- Complexity 6192 6195 +3
============================================
Files 286 286
Lines 25630 25636 +6
============================================
- Hits 7299 7296 -3
- Misses 18331 18340 +9 ☔ 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); |
| let forbiddenTags = ['SCRIPT', 'IFRAME', 'OBJECT', 'EMBED', 'BASE', 'LINK', 'META']; | ||
|
|
||
| template.content.querySelectorAll( '*' ).forEach( function( el ) { | ||
| if ( forbiddenTags.indexOf( el.tagName ) !== -1 ) { | ||
| el.remove(); | ||
| return; | ||
| } |
# Conflicts: # js/apbct-public-bundle.min.js # js/apbct-public-bundle_ext-protection.min.js # js/apbct-public-bundle_ext-protection_gathering.min.js # js/apbct-public-bundle_full-protection.min.js # js/apbct-public-bundle_full-protection_gathering.min.js # js/apbct-public-bundle_gathering.min.js # js/apbct-public-bundle_int-protection.min.js # js/apbct-public-bundle_int-protection_gathering.min.js # lib/Cleantalk/ApbctWP/RemoteCalls.php
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 20 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
js/src/common-cleantalk-modal.js:157
- Links generated with target="_blank" should include rel="noopener noreferrer" to prevent reverse-tabnabbing (and it is already allowed by the sanitizer via the "rel" attribute allowlist).
this.loaded.replace(urlRegex, '<a href="$1" target="_blank">$1</a>'),
lib/Cleantalk/ApbctWP/RemoteCalls.php:145
- The docstring says “an unauthorized request must never be able to trigger a DNS resolving”, but any external request can satisfy the “cheap checks” by sending
spbc_remote_call_action+plugin_name, so the guarantee is stronger than what the code enforces. Rewording avoids misleading future maintainers about the threat model.
* Performs DNS lookups, so it has to be called only after all the cheap checks passed -
* an unauthorized request must never be able to trigger a DNS resolving.
| isSafeCss: function( value ) { | ||
| let normalized = String( value ).replace( /[\u0000-\u0020\u007F]+/g, '' ).toLowerCase(); | ||
|
|
||
| return ! /(expression\(|javascript:|vbscript:|url\(|@import|-moz-binding|behavior:)/.test( normalized ); | ||
| }, |
There was a problem hiding this comment.
I think this is unnecessary.
AntonV1211
left a comment
There was a problem hiding this comment.
- piggy bank comments if you decide, then click on Resolve conversation
- The name of the commit consists of 3 blocks:
- this is the general nature of the edits (example Mod, Fix, Upd, New),
- where the edits were made (you can briefly specify the functionality - for example, SFW or the interface - for example, Settings, or if there are a lot of edits, you can simply write Code),
- What exactly was done in the edits
Currently, neither the commits in the edits nor the general name correspond to the names that are allowed in the release changelog.
- There is not even an attempt to make unit tests
https://app.doboard.com/1/task/52113