Fix plugin verification to detect directories without main PHP files
Summary
Fixes security issue where wp plugin verify-checksums skipped plugin directories without valid main plugin files, allowing malware to hide in these "shadow" directories.
Changes
1. Enhanced Plugin Detection (get_all_plugin_names())
- Scans wp-content/plugins/ filesystem for all plugin directories
- Includes both plugins with valid headers AND directories without headers
- Security measures:
- Directory readability validation
- Symlink exclusion to prevent traversal attacks
- Proper error handling
2. Enhanced Plugin Fetcher (UnfilteredPlugin::get())
- Detects plugin directories even when main files are missing
- Constructs conventional plugin file path for verification
3. Version Detection Fallback (detect_version_from_directory())
- Attempts to extract version from readme.txt (Stable tag)
- Falls back to scanning PHP files for Version header
- Security hardening:
- File size limit constant (1MB max)
- Readability checks before operations
- Error suppression to prevent path disclosure
- Proper glob() failure handling
4. User Warnings
- Clear warning when main plugin file is missing during verification
- Helps administrators identify potential security issues
5. Test Coverage
- Comprehensive test scenario for missing main file case
- Validates renamed files are detected
- Validates warnings are shown
- Tests both single plugin and --all flag scenarios
Security Impact
Before: Attackers could hide malicious files in plugin directories by removing/renaming the main plugin file. These directories were completely ignored during checksum verification.
After: All plugin directories are verified against WordPress.org checksums, regardless of whether they have valid plugin headers. Malware cannot hide using this technique.
Backward Compatibility
✅ Fully backward compatible - existing plugins with valid headers work exactly as before ✅ Only affects detection of plugin directories without valid main files (previously ignored) ✅ No breaking changes to API or command syntax
Files Changed
-
src/Checksum_Plugin_Command.php: +96 lines -
src/WP_CLI/Fetchers/UnfilteredPlugin.php: +11 lines -
features/checksum-plugin.feature: +34 lines (test)
Total: 141 lines added, 2 lines removed
Testing Status
- [x] PHP syntax validation passed
- [x] Code review completed and all issues addressed
- [x] Security hardening implemented
- [x] Code style issues fixed
- [ ] Behat tests (requires full test environment)
Original prompt
This section details on the original issue you should resolve
<issue_title>Verification checks skipped when plugin missing main PHP file</issue_title> <issue_description>Recently ran into a situation where a website had malware reinfection issues which required a bit of a deep dive to resolve. During the process I discovered that
wp plugin verify-checksums --allwill only check plugins which have their mainplugin.phpfile. For example, let's install a plugin then break the main file by renaming:wp plugin install wordfence mv wp-content/plugins/wordfence/wordfence.php wp-content/plugins/wordfence/wordfence.php.badNow if we try and run
wp plugin verify-checksums wordfencewe'll get the following:Warning: The 'wordfence' plugin could not be found. Error: You need to specify either one or more plugin slugs to check or use the --all flag to check all plugins.Also if we run
wp plugin verify-checksums --allit will saysuccessand not even attempt to run any checks on the/wordfence/directory. This is a problem as bad actors can use this method to hide files in these shadow plugin folders. Also there is no indication that these PHP files exist from/wp-admin/plugins.php.I think the solution should be to run checksums verifications based solely on the directory names. If a plugin directory matches a wordpress.org plugin then maybe run the verification checks?</issue_description>
Comments on the Issue (you are @copilot in this section)
@danielbachhuber Interesting report!I agree, this is worth putting together an enhancement for.</comment_new>
- Fixes wp-cli/checksum-command#121
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.
Codecov Report
:x: Patch coverage is 75.60976% with 10 lines in your changes missing coverage. Please review.
| Files with missing lines | Patch % | Lines |
|---|---|---|
| src/Checksum_Plugin_Command.php | 72.97% | 10 Missing :warning: |
:loudspeaker: Thoughts on this report? Let us know!
@copilot Code style issues:
- Empty ELSEIF statement detected
-
Call to function is_array() with array<int, string> will always evaluate to true.in src/Checksum_Plugin_Command.php:318
@copilot Code style issues:
- Empty ELSEIF statement detected ...
Fixed in commit 9a4be37:
- Removed empty elseif block - converted to standalone comment
- Removed redundant is_array() check - scandir() returns array|false, so checking
false !== $dirsis sufficient