Skip to content

Fix: add per-post capability check to add_ignore AJAX handler#1712

Merged
SteveJonesDev merged 10 commits into
mainfrom
fix/ajax-add-ignore-missing-capability-check
May 20, 2026
Merged

Fix: add per-post capability check to add_ignore AJAX handler#1712
SteveJonesDev merged 10 commits into
mainfrom
fix/ajax-add-ignore-missing-capability-check

Conversation

@SteveJonesDev
Copy link
Copy Markdown
Member

@SteveJonesDev SteveJonesDev commented May 20, 2026

Summary

  • The edac_insert_ignore_data AJAX action had no capability check beyond nonce verification, allowing any logged-in user to dismiss accessibility issues on posts they cannot edit
  • Added current_user_can('edit_post', $postid) for every post affected by the request — both the small-batch (per-ID lookup) and largeBatch (object-string site-wide lookup) paths
  • Matches the authorization model already enforced by the REST /dismiss-issue endpoint

Details

Small batch path: resolves DISTINCT postid for all supplied IDs in one query and checks edit_post on each before any UPDATE runs.

largeBatch path: resolves the object string from the first ID, then finds all DISTINCT postid values site-wide that share that object (matching exactly what the UPDATE … WHERE object = %s would touch), and checks edit_post on each.

Any failing check returns wp_send_json_error() with code -5 Permission Denied before the database is written.

Test plan

  • As an administrator, dismiss an issue — should succeed (success: true)
  • As a subscriber/editor without edit_post on the target post, send edac_insert_ignore_data with a valid nonce — should return success: false, code -5
  • Send a small batch with mixed post IDs (user can edit first post but not others) — should be rejected
  • Send a largeBatch request where the object string appears on posts the user cannot edit — should be rejected

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Tightened permission checks for bulk "ignore" updates: edit rights are now verified for each affected post and the operation halts if any unauthorized items are found.
    • Improved validation and handling of ignore targets and large-batch updates to reduce partial or invalid changes and improve reliability.
  • Documentation

    • Version bumped to 1.42.1 and changelog/readme updated.

Review Change Stack

pattonwebz and others added 3 commits May 19, 2026 16:53
Any logged-in user could call edac_insert_ignore_data to dismiss issues
on posts they cannot edit. Add current_user_can('edit_post') check using
the same postid lookup pattern as the REST /dismiss-issue endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The initial fix only checked the first issue ID. A mixed-ID batch or
a largeBatch request (which updates by object string site-wide) could
still affect posts the user cannot edit.

Now resolves all distinct postids for the full batch (or all posts
sharing the same object in largeBatch mode) and gates on edit_post
for every one before any UPDATE runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dcbb23cb-1804-4ffe-8353-f3d42f103564

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR hardens the Ajax::add_ignore() AJAX handler: it sanitizes inputs, validates the target ignore table, resolves affected posts (large- or small-batch queries), and enforces per-post current_user_can('edit_post', ...) before applying ignore updates.

Changes

Authorization and validation checkpoint

Layer / File(s) Summary
Authorization and table validation in add_ignore()
admin/class-ajax.php
add_ignore() now validates the ignore database table using edac_get_valid_table_name(), sanitizes inputs (ids, ignore_action, ignore_type), determines affected post IDs through either a large-batch lookup (using the first ID's object) or a small-batch id IN (...) query, and enforces current_user_can('edit_post', ...) for every affected post before proceeding. Returns -2 on invalid table/ID/object or no affected posts and -5 on authorization failure.
Release metadata and changelog
accessibility-checker.php, package.json, readme.txt, changelog.txt
Plugin and package version bumped to 1.42.1 and changelog/readme updated with the 2026-05-20 - version 1.42.1 entry describing improved permission handling for the ignore functionality.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

A rabbit checks each post with care,
Sanitizes IDs and skips the snare,
Batches and objects sorted neat,
Permissions checked before retreat,
Hops off happy — every ignore is fair 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding per-post capability checks to the add_ignore AJAX handler. It is specific, accurate, and reflects the primary security fix in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ajax-add-ignore-missing-capability-check

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances security in the add_ignore AJAX handler by implementing a capability check that verifies edit_post permissions for all affected posts. The implementation accounts for both large batch updates based on object strings and smaller batches based on specific IDs. A review comment suggested removing a redundant array type cast on the database query results to align with existing coding patterns in the file.

Comment thread admin/class-ajax.php Outdated
The SELECT object query was running twice for largeBatch requests — once
during the capability check and again in the execution block. Reuse
$batch_object to avoid the redundant query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@admin/class-ajax.php`:
- Around line 842-851: The permission check currently reads the authorized
object into $batch_object via $wpdb->get_var (using $valid_table and $first_id)
and builds $affected_post_ids, but later re-reads the object again before
performing the UPDATE which can cause a TOCTOU mismatch; modify the update path
to reuse the previously retrieved $batch_object (the value returned by
$wpdb->get_var and used to compute $affected_post_ids) instead of calling
$wpdb->get_var / re-querying the table again so the write operates on the exact
object set that was authorized.
- Around line 852-863: The handler currently proceeds as success when
$affected_post_ids is empty (e.g. all supplied $ids are stale), so add an
explicit check after building $affected_post_ids and before the permission loop:
if ( empty( $affected_post_ids ) ) call wp_send_json_error with the same '-2'
WP_Error used elsewhere to indicate "no matching items" (i.e., reject empty ID
lookups), ensuring you reference $ids, $affected_post_ids, wp_send_json_error
and preserve the existing permission checks using current_user_can.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6748950-0717-4bdf-b923-d9ff65032843

📥 Commits

Reviewing files that changed from the base of the PR and between 8c362f7 and e484363.

📒 Files selected for processing (1)
  • admin/class-ajax.php

Comment thread admin/class-ajax.php
Comment thread admin/class-ajax.php
SteveJonesDev and others added 2 commits May 20, 2026 14:02
- Return -2 error when $affected_post_ids is empty (stale IDs should not
  succeed silently) rather than letting the foreach no-op and responding success
- Remove redundant (array) cast; wpdb::get_col() always returns an array

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@SteveJonesDev SteveJonesDev changed the base branch from develop to main May 20, 2026 18:49
SteveJonesDev and others added 4 commits May 20, 2026 15:00
WP 7.0 added an `instanceof WP_Screen` guard to `get_current_screen()`
(wp-admin/includes/screen.php). The anonymous-class mocks assigned to
$GLOBALS['current_screen'] in EnqueueAdminTest and MetaBoxesTest no longer
satisfy that check, so `Helpers::is_block_editor()` short-circuits to false
and several enqueue/metabox tests fail.

`WP_Screen` is final, so we can't subclass it. Use `set_current_screen()`
to install a real screen and toggle the public `is_block_editor` property
to simulate block-editor vs classic-editor contexts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… side effects

set_current_screen() sets $hook_suffix, $typenow, and $taxnow, and fires
the current_screen action. Since tearDown() only unsets $GLOBALS['current_screen'],
those globals were left polluted between tests.

WP_Screen::get() returns a proper WP_Screen instance (satisfying the WP 7.0
instanceof check) without any of those side effects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tests: fix screen mocks for WP 7.0 (instanceof WP_Screen)
@SteveJonesDev SteveJonesDev merged commit f1fec1d into main May 20, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants