Harden interactive task handlers - #775
Draft
ilicfilip wants to merge 3 commits into
Draft
Conversation
Five AIOSEO interactive task handlers verified that the plugin was active and that the nonce was valid, but did not check the user's capability: - Archive_Author - Archive_Date - Crawl_Settings_Feed_Comments - Crawl_Settings_Feed_Authors - Media_Pages They now call the existing verify_aioseo_ajax_security() helper, which runs the active check, the capability check and the nonce check together. Every other settings-changing interactive handler in the plugin already required manage_options, so this brings the AIOSEO handlers in line with the existing convention. Email_Sending::enqueue_scripts() also overrode its parent without carrying over the parent's capability guard. It now checks $this->capability_required(), which resolves to the capability the provider actually declares (manage_options here, inherited from Tasks) rather than a hardcoded literal, so it stays correct if a subclass overrides CAPABILITY. Also switch esc_attr() to esc_url() on three hrefs. esc_url is the correct escaper for a link target, since esc_attr does not validate the URL scheme. Verified: PHPCS clean, parallel-lint clean, PHPStan clean, PHPUnit 398 tests / 1203 assertions passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Remove_Terms_Without_Posts and Update_Term_Description acted on
whatever term_id/taxonomy the request supplied, without checking it
against the term the task actually suggested.
The relevant constraints already existed in the task generation path,
they just were not applied when acting on a request:
maybe_remove_irrelevant_tasks() skips non-public taxonomies and drops
tasks once a term has more than MIN_POSTS posts.
Both handlers now, after the existing capability and nonce checks:
- reject non-public taxonomies,
- require a task from the same provider that targets this exact
term_id/taxonomy pair,
- and, for deletion, re-check the post count at submit time, since
the term may have gained posts after the task was created.
Binding rather than raising the required capability keeps the feature
working for its intended audience: these tasks are meant for Editors,
and the JS already submits the task's own target_term_id and
target_taxonomy, so the legitimate flow is unchanged.
Adds regression tests covering both the rejected and the permitted
paths. Verified that the negative tests fail when the new guards are
removed, and that the positive tests still pass, so they exercise the
behaviour rather than passing vacuously.
Verified: PHPCS clean, PHPStan clean, PHPUnit 404 tests / 1216
assertions passing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The site icon and Yoast organization logo pickers assembled their preview <img> by concatenating attachment metadata into an HTML string and assigning it to innerHTML. Both now build the node with createElement and property assignment, so no HTML string is parsed and attachment metadata is always handled as an attribute value rather than as markup. This follows the same reasoning as the existing textContent usage in updateTaskTitle(). Verified with jsdom that metadata containing quote characters round-trips as a value and does not become markup. Note: the Yoast logo picker's alt fallback still reads "Site icon preview", which looks like a copy-paste artifact. Left as-is to avoid mixing a user-visible string change into this commit. Verified: JS lint clean, PHPCS clean, PHPUnit 404 tests / 1216 assertions passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Test on Playground |
Contributor
✅ Code Coverage Report
🎉 Great job maintaining/improving code coverage! 📊 File-level Coverage Changes (2 files)📈 Coverage Improved
ℹ️ About this report
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hardening pass across the interactive task handlers, following an external code review of the plugin. Some of the reviewed items were re-scoped after verifying them against the code; the detailed analysis lives in our internal tracker.
Changes
Consistent capability checks on AIOSEO interactive tasks
Five AIOSEO handlers were verifying the plugin was active and the nonce was valid, but not the user's capability. They now call the shared
verify_aioseo_ajax_security()helper, which covers all three. Every other settings-changing interactive handler already requiredmanage_options, so this brings the AIOSEO ones in line with the existing convention rather than introducing a new policy.Capability guard on the email-sending task's script enqueue
Email_Sending::enqueue_scripts()overrode its parent without carrying over the parent's capability guard. It now checks$this->capability_required()— the provider's own declared capability rather than a hardcoded literal, so it stays correct if a subclass overridesCAPABILITY.Term task handlers bound to the term the task suggested
Remove_Terms_Without_PostsandUpdate_Term_Descriptionacted on whateverterm_id/taxonomythe request supplied. They now require that a task from the same provider actually targets that term, reject non-public taxonomies, and (for deletion) re-check the post count at submit time.The constraints already existed when generating the tasks —
maybe_remove_irrelevant_tasks()skips non-public taxonomies and drops tasks once a term exceedsMIN_POSTS— they just weren't applied when acting on the request. The count re-check also closes a genuine race: a term can gain posts between the task being created and the user clicking through.Binding rather than raising the required capability keeps the feature working for its intended audience. These tasks are meant for Editors, and the JS already submits the task's own
target_term_id/target_taxonomy, so the legitimate flow is unchanged.esc_url()for hrefsSwitched
esc_attr()toesc_url()on three links.esc_urlis the correct escaper for anhref, sinceesc_attrdoes not validate the URL scheme.Media previews built via DOM instead of HTML strings
The site icon and Yoast organization logo pickers assembled their preview
<img>by string concatenation and assigned it toinnerHTML. They now usecreateElementwith property assignment, so attachment metadata is never parsed as markup. Same reasoning as the existingtextContentusage inupdateTaskTitle().Testing
tests/phpunit/test-class-term-task-binding.phpadds 6 tests for the term task binding, covering both the rejected and the permitted paths — the latter so the binding can't silently break the feature.These were validated by temporarily removing the new guards and confirming the four negative tests fail while both positive tests still pass, i.e. they exercise the behaviour rather than passing vacuously.
The media preview change was verified with jsdom: attachment metadata containing quote characters round-trips as an attribute value and never becomes markup.
Verified: PHPCS clean · PHPStan clean · JS/CSS lint clean · PHPUnit 404 tests / 1216 assertions passing.
🤖 Generated with Claude Code