This repository was archived by the owner on Feb 5, 2026. It is now read-only.

docs: Improve permission checking documentation and examples - #95

Open
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean
Open

docs: Improve permission checking documentation and examples#95
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean

Conversation

@Ref34t

@Ref34tRef34t commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Summary

Changes Made

Documentation Improvements

  • docs/2.getting-started.md: Replaced 22-line manual permission checking example with clean 8-line execute() pattern
  • docs/4.using-abilities.md: Eliminated manual permission checking anti-patterns, added advanced error handling section showing how to handle specific error types

REST API Controller Update

  • includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php: Updated permission check to use strict comparison (true !==) for consistency with execute() pattern

Discussion Point

As @gziolo noted, this isn't a security vulnerability since each ability's execute() method already performs comprehensive permission checking. The REST API controller creates a two-layer permission model:

  1. REST API layer: Pre-check via run_ability_permissions_check()
  2. Ability execution layer: Full check via execute()

The controller's permission callback is architecturally unique because it dynamically checks permissions for any ability. We could consider simplifying by removing the separate permission callback and relying entirely on execute()'s error handling.

Open to feedback on this approach.

Test plan

  • Verify documentation examples follow recommended execute() pattern
  • Confirm no manual permission checking anti-patterns remain in docs
  • Test REST API endpoint permission handling

@github-actions

github-actionsBot commented Sep 30, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecovBot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.20%. Comparing base (9da857e) to head (27b7b1a).

Additional details and impacted files
@@ Coverage Diff @@## trunk #95 +/- ##
============================================
+ Coverage 90.59% 94.20% +3.60% 
============================================
Files 20 7 -13 Lines 1489 328 -1161 Branches 117 116 -1 ============================================
- Hits 1349 309 -1040 + Misses 140 19 -121 
FlagCoverage Δ
javascript94.20% <ø> (ø)
unit?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo

Copy link
Copy Markdown
Member

REST API security vulnerability

I don't think we are really experiencing a security vulnerability here, because every ability handles permissions check individually during execution as discussed in #76 (comment). That being said, we can iterate on the handling because the run controller is unique, as its permissions check model differs for each ability.

@gziologziolo added the [Type] Bug Something isn't working label Sep 30, 2025
@gziolo

Copy link
Copy Markdown
Member

This PR needs a rebase to resolve conflicts after #94 landed. This would help to review the changes proposed in this PR.

@Ref34t

Copy link
Copy Markdown
ContributorAuthor

@gziolo on it

… examples
- Eliminate all manual permission checking examples that could lead to security vulnerabilities
- Add advanced error handling section with specific error code handling
- Update variable names for consistency across examples
- Address @justlevine's feedback about documentation anti-patterns
Change permission check from loose to strict comparison in run_ability_permissions_check().
The previous pattern `if ( ! $ability->check_permission( $input ) )` would incorrectly
pass WP_Error objects as truthy, potentially allowing unauthorized access.
Fixed by using `if ( true !== $ability->check_permission( $input ) )` to properly
handle both false and WP_Error return values.
The strict permission checking now correctly catches input validation
failures in the permission check phase, returning 403 instead of
allowing invalid requests to proceed to execution-specific validation.
Updated 4 failing tests to expect 403 status and appropriate error codes:
- test_resource_ability_requires_get
- test_get_request_with_non_array_input
- test_post_request_with_non_array_input
- test_input_validation_failure_returns_error
@Ref34t
Ref34tforce-pushed the feature/fix-documentation-clean branch from 9df7d76 to bb906c3CompareOctober 1, 2025 08:05
@Ref34tRef34t changed the title Fix documentation anti-patterns and REST API security vulnerabilitydocs: Improve permission checking documentation and examplesOct 1, 2025
@Ref34t

Ref34t commented Oct 1, 2025

Copy link
Copy Markdown
ContributorAuthor

I've updated the PR title and description - you're right that this isn't a security vulnerability.

The reason: even if the REST API permission check had the WP_Error truthiness issue, execute() on line 136 would still block unauthorized access since it performs its own permission check internally.

This update improves code correctness by using strict comparison (true !==) to properly handle all return types from check_permissions(), but it's not fixing an actual security hole due to the two-layer permission model.

Comment threaddocs/4.using-abilities.md Outdated
@jonathanbossenger

Copy link
Copy Markdown
Contributor

I'm in the process of updating documentation in preparation for the 6.9 merge request in #117, so once that's merged, I'll come back here to check what still needs to be included.


$input = $this->get_input_from_request( $request );
if ( ! $ability->check_permissions( $input ) ) {
if ( true !== $ability->check_permissions( $input ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sharing relevant feedback that I initially mentioned in WordPress/wordpress-develop#9410 (comment): I don't think we should replace a contextually more specific error (from the actual permission callback) with a generic "Sorry you can't do this" error.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch! I've updated the code to preserve specific error messages from the permission callback instead of replacing them with a generic error.

The permission check now:

  1. Returns the WP_Error directly if check_permissions() returns one (preserves context)
  2. Only falls back to the generic error if it returns false

resolved here https://github.com/WordPress/abilities api/pull/95/commits/27b7b1a8215023b128cbcc55a27f51ac23e8774c

@jonathanbossengerjonathanbossenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor documentation suggestions.

Also, the docs folder structure has been updated in #117, so you might have to fetch those updates.

Comment threaddocs/4.using-abilities.md Outdated
Comment threaddocs/4.using-abilities.md Outdated
Don't replace contextually specific errors from check_permissions() with
generic 'Sorry, you are not allowed' error. If check_permissions() returns
a WP_Error, preserve and return it directly. Only use generic error as
fallback when permission check returns false.
@gziolo

Copy link
Copy Markdown
Member

It looks like the changes applied in this PR after the rebase are no longer visible. @Ref34t, is there still anything we should fix in the docs? All the issues raised for the permission checks in the code were fixed during merge to WordPress core, and they are now being synced with #126.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] BugSomething isn't working

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants

@Ref34t@gziolo@jonathanbossenger@felixarntz@justlevine@mokhaled-9h
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

docs: Improve permission checking documentation and examples - #95

Open
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean
Open

docs: Improve permission checking documentation and examples#95
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean

Conversation

@Ref34t

@Ref34tRef34t commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Summary

Changes Made

Documentation Improvements

  • docs/2.getting-started.md: Replaced 22-line manual permission checking example with clean 8-line execute() pattern
  • docs/4.using-abilities.md: Eliminated manual permission checking anti-patterns, added advanced error handling section showing how to handle specific error types

REST API Controller Update

  • includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php: Updated permission check to use strict comparison (true !==) for consistency with execute() pattern

Discussion Point

As @gziolo noted, this isn't a security vulnerability since each ability's execute() method already performs comprehensive permission checking. The REST API controller creates a two-layer permission model:

  1. REST API layer: Pre-check via run_ability_permissions_check()
  2. Ability execution layer: Full check via execute()

The controller's permission callback is architecturally unique because it dynamically checks permissions for any ability. We could consider simplifying by removing the separate permission callback and relying entirely on execute()'s error handling.

Open to feedback on this approach.

Test plan

  • Verify documentation examples follow recommended execute() pattern
  • Confirm no manual permission checking anti-patterns remain in docs
  • Test REST API endpoint permission handling

@github-actions

github-actionsBot commented Sep 30, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecovBot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.20%. Comparing base (9da857e) to head (27b7b1a).

Additional details and impacted files
@@ Coverage Diff @@## trunk #95 +/- ##
============================================
+ Coverage 90.59% 94.20% +3.60% 
============================================
Files 20 7 -13 Lines 1489 328 -1161 Branches 117 116 -1 ============================================
- Hits 1349 309 -1040 + Misses 140 19 -121 
FlagCoverage Δ
javascript94.20% <ø> (ø)
unit?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo

Copy link
Copy Markdown
Member

REST API security vulnerability

I don't think we are really experiencing a security vulnerability here, because every ability handles permissions check individually during execution as discussed in #76 (comment). That being said, we can iterate on the handling because the run controller is unique, as its permissions check model differs for each ability.

@gziologziolo added the [Type] Bug Something isn't working label Sep 30, 2025
@gziolo

Copy link
Copy Markdown
Member

This PR needs a rebase to resolve conflicts after #94 landed. This would help to review the changes proposed in this PR.

@Ref34t

Copy link
Copy Markdown
ContributorAuthor

@gziolo on it

… examples
- Eliminate all manual permission checking examples that could lead to security vulnerabilities
- Add advanced error handling section with specific error code handling
- Update variable names for consistency across examples
- Address @justlevine's feedback about documentation anti-patterns
Change permission check from loose to strict comparison in run_ability_permissions_check().
The previous pattern `if ( ! $ability->check_permission( $input ) )` would incorrectly
pass WP_Error objects as truthy, potentially allowing unauthorized access.
Fixed by using `if ( true !== $ability->check_permission( $input ) )` to properly
handle both false and WP_Error return values.
The strict permission checking now correctly catches input validation
failures in the permission check phase, returning 403 instead of
allowing invalid requests to proceed to execution-specific validation.
Updated 4 failing tests to expect 403 status and appropriate error codes:
- test_resource_ability_requires_get
- test_get_request_with_non_array_input
- test_post_request_with_non_array_input
- test_input_validation_failure_returns_error
@Ref34t
Ref34tforce-pushed the feature/fix-documentation-clean branch from 9df7d76 to bb906c3CompareOctober 1, 2025 08:05
@Ref34tRef34t changed the title Fix documentation anti-patterns and REST API security vulnerabilitydocs: Improve permission checking documentation and examplesOct 1, 2025
@Ref34t

Ref34t commented Oct 1, 2025

Copy link
Copy Markdown
ContributorAuthor

I've updated the PR title and description - you're right that this isn't a security vulnerability.

The reason: even if the REST API permission check had the WP_Error truthiness issue, execute() on line 136 would still block unauthorized access since it performs its own permission check internally.

This update improves code correctness by using strict comparison (true !==) to properly handle all return types from check_permissions(), but it's not fixing an actual security hole due to the two-layer permission model.

Comment threaddocs/4.using-abilities.md Outdated
@jonathanbossenger

Copy link
Copy Markdown
Contributor

I'm in the process of updating documentation in preparation for the 6.9 merge request in #117, so once that's merged, I'll come back here to check what still needs to be included.


$input = $this->get_input_from_request( $request );
if ( ! $ability->check_permissions( $input ) ) {
if ( true !== $ability->check_permissions( $input ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sharing relevant feedback that I initially mentioned in WordPress/wordpress-develop#9410 (comment): I don't think we should replace a contextually more specific error (from the actual permission callback) with a generic "Sorry you can't do this" error.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch! I've updated the code to preserve specific error messages from the permission callback instead of replacing them with a generic error.

The permission check now:

  1. Returns the WP_Error directly if check_permissions() returns one (preserves context)
  2. Only falls back to the generic error if it returns false

resolved here https://github.com/WordPress/abilities api/pull/95/commits/27b7b1a8215023b128cbcc55a27f51ac23e8774c

@jonathanbossengerjonathanbossenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor documentation suggestions.

Also, the docs folder structure has been updated in #117, so you might have to fetch those updates.

Comment threaddocs/4.using-abilities.md Outdated
Comment threaddocs/4.using-abilities.md Outdated
Don't replace contextually specific errors from check_permissions() with
generic 'Sorry, you are not allowed' error. If check_permissions() returns
a WP_Error, preserve and return it directly. Only use generic error as
fallback when permission check returns false.
@gziolo

Copy link
Copy Markdown
Member

It looks like the changes applied in this PR after the rebase are no longer visible. @Ref34t, is there still anything we should fix in the docs? All the issues raised for the permission checks in the code were fixed during merge to WordPress core, and they are now being synced with #126.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] BugSomething isn't working

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants

@Ref34t@gziolo@jonathanbossenger@felixarntz@justlevine@mokhaled-9h
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

docs: Improve permission checking documentation and examples - #95

Open
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean
Open

docs: Improve permission checking documentation and examples#95
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean

Conversation

@Ref34t

@Ref34tRef34t commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Summary

Changes Made

Documentation Improvements

  • docs/2.getting-started.md: Replaced 22-line manual permission checking example with clean 8-line execute() pattern
  • docs/4.using-abilities.md: Eliminated manual permission checking anti-patterns, added advanced error handling section showing how to handle specific error types

REST API Controller Update

  • includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php: Updated permission check to use strict comparison (true !==) for consistency with execute() pattern

Discussion Point

As @gziolo noted, this isn't a security vulnerability since each ability's execute() method already performs comprehensive permission checking. The REST API controller creates a two-layer permission model:

  1. REST API layer: Pre-check via run_ability_permissions_check()
  2. Ability execution layer: Full check via execute()

The controller's permission callback is architecturally unique because it dynamically checks permissions for any ability. We could consider simplifying by removing the separate permission callback and relying entirely on execute()'s error handling.

Open to feedback on this approach.

Test plan

  • Verify documentation examples follow recommended execute() pattern
  • Confirm no manual permission checking anti-patterns remain in docs
  • Test REST API endpoint permission handling

@github-actions

github-actionsBot commented Sep 30, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecovBot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.20%. Comparing base (9da857e) to head (27b7b1a).

Additional details and impacted files
@@ Coverage Diff @@## trunk #95 +/- ##
============================================
+ Coverage 90.59% 94.20% +3.60% 
============================================
Files 20 7 -13 Lines 1489 328 -1161 Branches 117 116 -1 ============================================
- Hits 1349 309 -1040 + Misses 140 19 -121 
FlagCoverage Δ
javascript94.20% <ø> (ø)
unit?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo

Copy link
Copy Markdown
Member

REST API security vulnerability

I don't think we are really experiencing a security vulnerability here, because every ability handles permissions check individually during execution as discussed in #76 (comment). That being said, we can iterate on the handling because the run controller is unique, as its permissions check model differs for each ability.

@gziologziolo added the [Type] Bug Something isn't working label Sep 30, 2025
@gziolo

Copy link
Copy Markdown
Member

This PR needs a rebase to resolve conflicts after #94 landed. This would help to review the changes proposed in this PR.

@Ref34t

Copy link
Copy Markdown
ContributorAuthor

@gziolo on it

… examples
- Eliminate all manual permission checking examples that could lead to security vulnerabilities
- Add advanced error handling section with specific error code handling
- Update variable names for consistency across examples
- Address @justlevine's feedback about documentation anti-patterns
Change permission check from loose to strict comparison in run_ability_permissions_check().
The previous pattern `if ( ! $ability->check_permission( $input ) )` would incorrectly
pass WP_Error objects as truthy, potentially allowing unauthorized access.
Fixed by using `if ( true !== $ability->check_permission( $input ) )` to properly
handle both false and WP_Error return values.
The strict permission checking now correctly catches input validation
failures in the permission check phase, returning 403 instead of
allowing invalid requests to proceed to execution-specific validation.
Updated 4 failing tests to expect 403 status and appropriate error codes:
- test_resource_ability_requires_get
- test_get_request_with_non_array_input
- test_post_request_with_non_array_input
- test_input_validation_failure_returns_error
@Ref34t
Ref34tforce-pushed the feature/fix-documentation-clean branch from 9df7d76 to bb906c3CompareOctober 1, 2025 08:05
@Ref34tRef34t changed the title Fix documentation anti-patterns and REST API security vulnerabilitydocs: Improve permission checking documentation and examplesOct 1, 2025
@Ref34t

Ref34t commented Oct 1, 2025

Copy link
Copy Markdown
ContributorAuthor

I've updated the PR title and description - you're right that this isn't a security vulnerability.

The reason: even if the REST API permission check had the WP_Error truthiness issue, execute() on line 136 would still block unauthorized access since it performs its own permission check internally.

This update improves code correctness by using strict comparison (true !==) to properly handle all return types from check_permissions(), but it's not fixing an actual security hole due to the two-layer permission model.

Comment threaddocs/4.using-abilities.md Outdated
@jonathanbossenger

Copy link
Copy Markdown
Contributor

I'm in the process of updating documentation in preparation for the 6.9 merge request in #117, so once that's merged, I'll come back here to check what still needs to be included.


$input = $this->get_input_from_request( $request );
if ( ! $ability->check_permissions( $input ) ) {
if ( true !== $ability->check_permissions( $input ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sharing relevant feedback that I initially mentioned in WordPress/wordpress-develop#9410 (comment): I don't think we should replace a contextually more specific error (from the actual permission callback) with a generic "Sorry you can't do this" error.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch! I've updated the code to preserve specific error messages from the permission callback instead of replacing them with a generic error.

The permission check now:

  1. Returns the WP_Error directly if check_permissions() returns one (preserves context)
  2. Only falls back to the generic error if it returns false

resolved here https://github.com/WordPress/abilities api/pull/95/commits/27b7b1a8215023b128cbcc55a27f51ac23e8774c

@jonathanbossengerjonathanbossenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor documentation suggestions.

Also, the docs folder structure has been updated in #117, so you might have to fetch those updates.

Comment threaddocs/4.using-abilities.md Outdated
Comment threaddocs/4.using-abilities.md Outdated
Don't replace contextually specific errors from check_permissions() with
generic 'Sorry, you are not allowed' error. If check_permissions() returns
a WP_Error, preserve and return it directly. Only use generic error as
fallback when permission check returns false.
@gziolo

Copy link
Copy Markdown
Member

It looks like the changes applied in this PR after the rebase are no longer visible. @Ref34t, is there still anything we should fix in the docs? All the issues raised for the permission checks in the code were fixed during merge to WordPress core, and they are now being synced with #126.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] BugSomething isn't working

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants

@Ref34t@gziolo@jonathanbossenger@felixarntz@justlevine@mokhaled-9h
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

docs: Improve permission checking documentation and examples - #95

Open
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean
Open

docs: Improve permission checking documentation and examples#95
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean

Conversation

@Ref34t

@Ref34tRef34t commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Summary

Changes Made

Documentation Improvements

  • docs/2.getting-started.md: Replaced 22-line manual permission checking example with clean 8-line execute() pattern
  • docs/4.using-abilities.md: Eliminated manual permission checking anti-patterns, added advanced error handling section showing how to handle specific error types

REST API Controller Update

  • includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php: Updated permission check to use strict comparison (true !==) for consistency with execute() pattern

Discussion Point

As @gziolo noted, this isn't a security vulnerability since each ability's execute() method already performs comprehensive permission checking. The REST API controller creates a two-layer permission model:

  1. REST API layer: Pre-check via run_ability_permissions_check()
  2. Ability execution layer: Full check via execute()

The controller's permission callback is architecturally unique because it dynamically checks permissions for any ability. We could consider simplifying by removing the separate permission callback and relying entirely on execute()'s error handling.

Open to feedback on this approach.

Test plan

  • Verify documentation examples follow recommended execute() pattern
  • Confirm no manual permission checking anti-patterns remain in docs
  • Test REST API endpoint permission handling

@github-actions

github-actionsBot commented Sep 30, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecovBot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.20%. Comparing base (9da857e) to head (27b7b1a).

Additional details and impacted files
@@ Coverage Diff @@## trunk #95 +/- ##
============================================
+ Coverage 90.59% 94.20% +3.60% 
============================================
Files 20 7 -13 Lines 1489 328 -1161 Branches 117 116 -1 ============================================
- Hits 1349 309 -1040 + Misses 140 19 -121 
FlagCoverage Δ
javascript94.20% <ø> (ø)
unit?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo

Copy link
Copy Markdown
Member

REST API security vulnerability

I don't think we are really experiencing a security vulnerability here, because every ability handles permissions check individually during execution as discussed in #76 (comment). That being said, we can iterate on the handling because the run controller is unique, as its permissions check model differs for each ability.

@gziologziolo added the [Type] Bug Something isn't working label Sep 30, 2025
@gziolo

Copy link
Copy Markdown
Member

This PR needs a rebase to resolve conflicts after #94 landed. This would help to review the changes proposed in this PR.

@Ref34t

Copy link
Copy Markdown
ContributorAuthor

@gziolo on it

… examples
- Eliminate all manual permission checking examples that could lead to security vulnerabilities
- Add advanced error handling section with specific error code handling
- Update variable names for consistency across examples
- Address @justlevine's feedback about documentation anti-patterns
Change permission check from loose to strict comparison in run_ability_permissions_check().
The previous pattern `if ( ! $ability->check_permission( $input ) )` would incorrectly
pass WP_Error objects as truthy, potentially allowing unauthorized access.
Fixed by using `if ( true !== $ability->check_permission( $input ) )` to properly
handle both false and WP_Error return values.
The strict permission checking now correctly catches input validation
failures in the permission check phase, returning 403 instead of
allowing invalid requests to proceed to execution-specific validation.
Updated 4 failing tests to expect 403 status and appropriate error codes:
- test_resource_ability_requires_get
- test_get_request_with_non_array_input
- test_post_request_with_non_array_input
- test_input_validation_failure_returns_error
@Ref34t
Ref34tforce-pushed the feature/fix-documentation-clean branch from 9df7d76 to bb906c3CompareOctober 1, 2025 08:05
@Ref34tRef34t changed the title Fix documentation anti-patterns and REST API security vulnerabilitydocs: Improve permission checking documentation and examplesOct 1, 2025
@Ref34t

Ref34t commented Oct 1, 2025

Copy link
Copy Markdown
ContributorAuthor

I've updated the PR title and description - you're right that this isn't a security vulnerability.

The reason: even if the REST API permission check had the WP_Error truthiness issue, execute() on line 136 would still block unauthorized access since it performs its own permission check internally.

This update improves code correctness by using strict comparison (true !==) to properly handle all return types from check_permissions(), but it's not fixing an actual security hole due to the two-layer permission model.

Comment threaddocs/4.using-abilities.md Outdated
@jonathanbossenger

Copy link
Copy Markdown
Contributor

I'm in the process of updating documentation in preparation for the 6.9 merge request in #117, so once that's merged, I'll come back here to check what still needs to be included.


$input = $this->get_input_from_request( $request );
if ( ! $ability->check_permissions( $input ) ) {
if ( true !== $ability->check_permissions( $input ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sharing relevant feedback that I initially mentioned in WordPress/wordpress-develop#9410 (comment): I don't think we should replace a contextually more specific error (from the actual permission callback) with a generic "Sorry you can't do this" error.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch! I've updated the code to preserve specific error messages from the permission callback instead of replacing them with a generic error.

The permission check now:

  1. Returns the WP_Error directly if check_permissions() returns one (preserves context)
  2. Only falls back to the generic error if it returns false

resolved here https://github.com/WordPress/abilities api/pull/95/commits/27b7b1a8215023b128cbcc55a27f51ac23e8774c

@jonathanbossengerjonathanbossenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor documentation suggestions.

Also, the docs folder structure has been updated in #117, so you might have to fetch those updates.

Comment threaddocs/4.using-abilities.md Outdated
Comment threaddocs/4.using-abilities.md Outdated
Don't replace contextually specific errors from check_permissions() with
generic 'Sorry, you are not allowed' error. If check_permissions() returns
a WP_Error, preserve and return it directly. Only use generic error as
fallback when permission check returns false.
@gziolo

Copy link
Copy Markdown
Member

It looks like the changes applied in this PR after the rebase are no longer visible. @Ref34t, is there still anything we should fix in the docs? All the issues raised for the permission checks in the code were fixed during merge to WordPress core, and they are now being synced with #126.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] BugSomething isn't working

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants

@Ref34t@gziolo@jonathanbossenger@felixarntz@justlevine@mokhaled-9h
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

docs: Improve permission checking documentation and examples - #95

Open
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean
Open

docs: Improve permission checking documentation and examples#95
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean

Conversation

@Ref34t

@Ref34tRef34t commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Summary

Changes Made

Documentation Improvements

  • docs/2.getting-started.md: Replaced 22-line manual permission checking example with clean 8-line execute() pattern
  • docs/4.using-abilities.md: Eliminated manual permission checking anti-patterns, added advanced error handling section showing how to handle specific error types

REST API Controller Update

  • includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php: Updated permission check to use strict comparison (true !==) for consistency with execute() pattern

Discussion Point

As @gziolo noted, this isn't a security vulnerability since each ability's execute() method already performs comprehensive permission checking. The REST API controller creates a two-layer permission model:

  1. REST API layer: Pre-check via run_ability_permissions_check()
  2. Ability execution layer: Full check via execute()

The controller's permission callback is architecturally unique because it dynamically checks permissions for any ability. We could consider simplifying by removing the separate permission callback and relying entirely on execute()'s error handling.

Open to feedback on this approach.

Test plan

  • Verify documentation examples follow recommended execute() pattern
  • Confirm no manual permission checking anti-patterns remain in docs
  • Test REST API endpoint permission handling

@github-actions

github-actionsBot commented Sep 30, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecovBot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.20%. Comparing base (9da857e) to head (27b7b1a).

Additional details and impacted files
@@ Coverage Diff @@## trunk #95 +/- ##
============================================
+ Coverage 90.59% 94.20% +3.60% 
============================================
Files 20 7 -13 Lines 1489 328 -1161 Branches 117 116 -1 ============================================
- Hits 1349 309 -1040 + Misses 140 19 -121 
FlagCoverage Δ
javascript94.20% <ø> (ø)
unit?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo

Copy link
Copy Markdown
Member

REST API security vulnerability

I don't think we are really experiencing a security vulnerability here, because every ability handles permissions check individually during execution as discussed in #76 (comment). That being said, we can iterate on the handling because the run controller is unique, as its permissions check model differs for each ability.

@gziologziolo added the [Type] Bug Something isn't working label Sep 30, 2025
@gziolo

Copy link
Copy Markdown
Member

This PR needs a rebase to resolve conflicts after #94 landed. This would help to review the changes proposed in this PR.

@Ref34t

Copy link
Copy Markdown
ContributorAuthor

@gziolo on it

… examples
- Eliminate all manual permission checking examples that could lead to security vulnerabilities
- Add advanced error handling section with specific error code handling
- Update variable names for consistency across examples
- Address @justlevine's feedback about documentation anti-patterns
Change permission check from loose to strict comparison in run_ability_permissions_check().
The previous pattern `if ( ! $ability->check_permission( $input ) )` would incorrectly
pass WP_Error objects as truthy, potentially allowing unauthorized access.
Fixed by using `if ( true !== $ability->check_permission( $input ) )` to properly
handle both false and WP_Error return values.
The strict permission checking now correctly catches input validation
failures in the permission check phase, returning 403 instead of
allowing invalid requests to proceed to execution-specific validation.
Updated 4 failing tests to expect 403 status and appropriate error codes:
- test_resource_ability_requires_get
- test_get_request_with_non_array_input
- test_post_request_with_non_array_input
- test_input_validation_failure_returns_error
@Ref34t
Ref34tforce-pushed the feature/fix-documentation-clean branch from 9df7d76 to bb906c3CompareOctober 1, 2025 08:05
@Ref34tRef34t changed the title Fix documentation anti-patterns and REST API security vulnerabilitydocs: Improve permission checking documentation and examplesOct 1, 2025
@Ref34t

Ref34t commented Oct 1, 2025

Copy link
Copy Markdown
ContributorAuthor

I've updated the PR title and description - you're right that this isn't a security vulnerability.

The reason: even if the REST API permission check had the WP_Error truthiness issue, execute() on line 136 would still block unauthorized access since it performs its own permission check internally.

This update improves code correctness by using strict comparison (true !==) to properly handle all return types from check_permissions(), but it's not fixing an actual security hole due to the two-layer permission model.

Comment threaddocs/4.using-abilities.md Outdated
@jonathanbossenger

Copy link
Copy Markdown
Contributor

I'm in the process of updating documentation in preparation for the 6.9 merge request in #117, so once that's merged, I'll come back here to check what still needs to be included.


$input = $this->get_input_from_request( $request );
if ( ! $ability->check_permissions( $input ) ) {
if ( true !== $ability->check_permissions( $input ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sharing relevant feedback that I initially mentioned in WordPress/wordpress-develop#9410 (comment): I don't think we should replace a contextually more specific error (from the actual permission callback) with a generic "Sorry you can't do this" error.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch! I've updated the code to preserve specific error messages from the permission callback instead of replacing them with a generic error.

The permission check now:

  1. Returns the WP_Error directly if check_permissions() returns one (preserves context)
  2. Only falls back to the generic error if it returns false

resolved here https://github.com/WordPress/abilities api/pull/95/commits/27b7b1a8215023b128cbcc55a27f51ac23e8774c

@jonathanbossengerjonathanbossenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor documentation suggestions.

Also, the docs folder structure has been updated in #117, so you might have to fetch those updates.

Comment threaddocs/4.using-abilities.md Outdated
Comment threaddocs/4.using-abilities.md Outdated
Don't replace contextually specific errors from check_permissions() with
generic 'Sorry, you are not allowed' error. If check_permissions() returns
a WP_Error, preserve and return it directly. Only use generic error as
fallback when permission check returns false.
@gziolo

Copy link
Copy Markdown
Member

It looks like the changes applied in this PR after the rebase are no longer visible. @Ref34t, is there still anything we should fix in the docs? All the issues raised for the permission checks in the code were fixed during merge to WordPress core, and they are now being synced with #126.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] BugSomething isn't working

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants

@Ref34t@gziolo@jonathanbossenger@felixarntz@justlevine@mokhaled-9h
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

docs: Improve permission checking documentation and examples - #95

Open
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean
Open

docs: Improve permission checking documentation and examples#95
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean

Conversation

@Ref34t

@Ref34tRef34t commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Summary

Changes Made

Documentation Improvements

  • docs/2.getting-started.md: Replaced 22-line manual permission checking example with clean 8-line execute() pattern
  • docs/4.using-abilities.md: Eliminated manual permission checking anti-patterns, added advanced error handling section showing how to handle specific error types

REST API Controller Update

  • includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php: Updated permission check to use strict comparison (true !==) for consistency with execute() pattern

Discussion Point

As @gziolo noted, this isn't a security vulnerability since each ability's execute() method already performs comprehensive permission checking. The REST API controller creates a two-layer permission model:

  1. REST API layer: Pre-check via run_ability_permissions_check()
  2. Ability execution layer: Full check via execute()

The controller's permission callback is architecturally unique because it dynamically checks permissions for any ability. We could consider simplifying by removing the separate permission callback and relying entirely on execute()'s error handling.

Open to feedback on this approach.

Test plan

  • Verify documentation examples follow recommended execute() pattern
  • Confirm no manual permission checking anti-patterns remain in docs
  • Test REST API endpoint permission handling

@github-actions

github-actionsBot commented Sep 30, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecovBot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.20%. Comparing base (9da857e) to head (27b7b1a).

Additional details and impacted files
@@ Coverage Diff @@## trunk #95 +/- ##
============================================
+ Coverage 90.59% 94.20% +3.60% 
============================================
Files 20 7 -13 Lines 1489 328 -1161 Branches 117 116 -1 ============================================
- Hits 1349 309 -1040 + Misses 140 19 -121 
FlagCoverage Δ
javascript94.20% <ø> (ø)
unit?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo

Copy link
Copy Markdown
Member

REST API security vulnerability

I don't think we are really experiencing a security vulnerability here, because every ability handles permissions check individually during execution as discussed in #76 (comment). That being said, we can iterate on the handling because the run controller is unique, as its permissions check model differs for each ability.

@gziologziolo added the [Type] Bug Something isn't working label Sep 30, 2025
@gziolo

Copy link
Copy Markdown
Member

This PR needs a rebase to resolve conflicts after #94 landed. This would help to review the changes proposed in this PR.

@Ref34t

Copy link
Copy Markdown
ContributorAuthor

@gziolo on it

… examples
- Eliminate all manual permission checking examples that could lead to security vulnerabilities
- Add advanced error handling section with specific error code handling
- Update variable names for consistency across examples
- Address @justlevine's feedback about documentation anti-patterns
Change permission check from loose to strict comparison in run_ability_permissions_check().
The previous pattern `if ( ! $ability->check_permission( $input ) )` would incorrectly
pass WP_Error objects as truthy, potentially allowing unauthorized access.
Fixed by using `if ( true !== $ability->check_permission( $input ) )` to properly
handle both false and WP_Error return values.
The strict permission checking now correctly catches input validation
failures in the permission check phase, returning 403 instead of
allowing invalid requests to proceed to execution-specific validation.
Updated 4 failing tests to expect 403 status and appropriate error codes:
- test_resource_ability_requires_get
- test_get_request_with_non_array_input
- test_post_request_with_non_array_input
- test_input_validation_failure_returns_error
@Ref34t
Ref34tforce-pushed the feature/fix-documentation-clean branch from 9df7d76 to bb906c3CompareOctober 1, 2025 08:05
@Ref34tRef34t changed the title Fix documentation anti-patterns and REST API security vulnerabilitydocs: Improve permission checking documentation and examplesOct 1, 2025
@Ref34t

Ref34t commented Oct 1, 2025

Copy link
Copy Markdown
ContributorAuthor

I've updated the PR title and description - you're right that this isn't a security vulnerability.

The reason: even if the REST API permission check had the WP_Error truthiness issue, execute() on line 136 would still block unauthorized access since it performs its own permission check internally.

This update improves code correctness by using strict comparison (true !==) to properly handle all return types from check_permissions(), but it's not fixing an actual security hole due to the two-layer permission model.

Comment threaddocs/4.using-abilities.md Outdated
@jonathanbossenger

Copy link
Copy Markdown
Contributor

I'm in the process of updating documentation in preparation for the 6.9 merge request in #117, so once that's merged, I'll come back here to check what still needs to be included.


$input = $this->get_input_from_request( $request );
if ( ! $ability->check_permissions( $input ) ) {
if ( true !== $ability->check_permissions( $input ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sharing relevant feedback that I initially mentioned in WordPress/wordpress-develop#9410 (comment): I don't think we should replace a contextually more specific error (from the actual permission callback) with a generic "Sorry you can't do this" error.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch! I've updated the code to preserve specific error messages from the permission callback instead of replacing them with a generic error.

The permission check now:

  1. Returns the WP_Error directly if check_permissions() returns one (preserves context)
  2. Only falls back to the generic error if it returns false

resolved here https://github.com/WordPress/abilities api/pull/95/commits/27b7b1a8215023b128cbcc55a27f51ac23e8774c

@jonathanbossengerjonathanbossenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor documentation suggestions.

Also, the docs folder structure has been updated in #117, so you might have to fetch those updates.

Comment threaddocs/4.using-abilities.md Outdated
Comment threaddocs/4.using-abilities.md Outdated
Don't replace contextually specific errors from check_permissions() with
generic 'Sorry, you are not allowed' error. If check_permissions() returns
a WP_Error, preserve and return it directly. Only use generic error as
fallback when permission check returns false.
@gziolo

Copy link
Copy Markdown
Member

It looks like the changes applied in this PR after the rebase are no longer visible. @Ref34t, is there still anything we should fix in the docs? All the issues raised for the permission checks in the code were fixed during merge to WordPress core, and they are now being synced with #126.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] BugSomething isn't working

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants

@Ref34t@gziolo@jonathanbossenger@felixarntz@justlevine@mokhaled-9h
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

docs: Improve permission checking documentation and examples - #95

Open
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean
Open

docs: Improve permission checking documentation and examples#95
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean

Conversation

@Ref34t

@Ref34tRef34t commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Summary

Changes Made

Documentation Improvements

  • docs/2.getting-started.md: Replaced 22-line manual permission checking example with clean 8-line execute() pattern
  • docs/4.using-abilities.md: Eliminated manual permission checking anti-patterns, added advanced error handling section showing how to handle specific error types

REST API Controller Update

  • includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php: Updated permission check to use strict comparison (true !==) for consistency with execute() pattern

Discussion Point

As @gziolo noted, this isn't a security vulnerability since each ability's execute() method already performs comprehensive permission checking. The REST API controller creates a two-layer permission model:

  1. REST API layer: Pre-check via run_ability_permissions_check()
  2. Ability execution layer: Full check via execute()

The controller's permission callback is architecturally unique because it dynamically checks permissions for any ability. We could consider simplifying by removing the separate permission callback and relying entirely on execute()'s error handling.

Open to feedback on this approach.

Test plan

  • Verify documentation examples follow recommended execute() pattern
  • Confirm no manual permission checking anti-patterns remain in docs
  • Test REST API endpoint permission handling

@github-actions

github-actionsBot commented Sep 30, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecovBot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.20%. Comparing base (9da857e) to head (27b7b1a).

Additional details and impacted files
@@ Coverage Diff @@## trunk #95 +/- ##
============================================
+ Coverage 90.59% 94.20% +3.60% 
============================================
Files 20 7 -13 Lines 1489 328 -1161 Branches 117 116 -1 ============================================
- Hits 1349 309 -1040 + Misses 140 19 -121 
FlagCoverage Δ
javascript94.20% <ø> (ø)
unit?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo

Copy link
Copy Markdown
Member

REST API security vulnerability

I don't think we are really experiencing a security vulnerability here, because every ability handles permissions check individually during execution as discussed in #76 (comment). That being said, we can iterate on the handling because the run controller is unique, as its permissions check model differs for each ability.

@gziologziolo added the [Type] Bug Something isn't working label Sep 30, 2025
@gziolo

Copy link
Copy Markdown
Member

This PR needs a rebase to resolve conflicts after #94 landed. This would help to review the changes proposed in this PR.

@Ref34t

Copy link
Copy Markdown
ContributorAuthor

@gziolo on it

… examples
- Eliminate all manual permission checking examples that could lead to security vulnerabilities
- Add advanced error handling section with specific error code handling
- Update variable names for consistency across examples
- Address @justlevine's feedback about documentation anti-patterns
Change permission check from loose to strict comparison in run_ability_permissions_check().
The previous pattern `if ( ! $ability->check_permission( $input ) )` would incorrectly
pass WP_Error objects as truthy, potentially allowing unauthorized access.
Fixed by using `if ( true !== $ability->check_permission( $input ) )` to properly
handle both false and WP_Error return values.
The strict permission checking now correctly catches input validation
failures in the permission check phase, returning 403 instead of
allowing invalid requests to proceed to execution-specific validation.
Updated 4 failing tests to expect 403 status and appropriate error codes:
- test_resource_ability_requires_get
- test_get_request_with_non_array_input
- test_post_request_with_non_array_input
- test_input_validation_failure_returns_error
@Ref34t
Ref34tforce-pushed the feature/fix-documentation-clean branch from 9df7d76 to bb906c3CompareOctober 1, 2025 08:05
@Ref34tRef34t changed the title Fix documentation anti-patterns and REST API security vulnerabilitydocs: Improve permission checking documentation and examplesOct 1, 2025
@Ref34t

Ref34t commented Oct 1, 2025

Copy link
Copy Markdown
ContributorAuthor

I've updated the PR title and description - you're right that this isn't a security vulnerability.

The reason: even if the REST API permission check had the WP_Error truthiness issue, execute() on line 136 would still block unauthorized access since it performs its own permission check internally.

This update improves code correctness by using strict comparison (true !==) to properly handle all return types from check_permissions(), but it's not fixing an actual security hole due to the two-layer permission model.

Comment threaddocs/4.using-abilities.md Outdated
@jonathanbossenger

Copy link
Copy Markdown
Contributor

I'm in the process of updating documentation in preparation for the 6.9 merge request in #117, so once that's merged, I'll come back here to check what still needs to be included.


$input = $this->get_input_from_request( $request );
if ( ! $ability->check_permissions( $input ) ) {
if ( true !== $ability->check_permissions( $input ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sharing relevant feedback that I initially mentioned in WordPress/wordpress-develop#9410 (comment): I don't think we should replace a contextually more specific error (from the actual permission callback) with a generic "Sorry you can't do this" error.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch! I've updated the code to preserve specific error messages from the permission callback instead of replacing them with a generic error.

The permission check now:

  1. Returns the WP_Error directly if check_permissions() returns one (preserves context)
  2. Only falls back to the generic error if it returns false

resolved here https://github.com/WordPress/abilities api/pull/95/commits/27b7b1a8215023b128cbcc55a27f51ac23e8774c

@jonathanbossengerjonathanbossenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor documentation suggestions.

Also, the docs folder structure has been updated in #117, so you might have to fetch those updates.

Comment threaddocs/4.using-abilities.md Outdated
Comment threaddocs/4.using-abilities.md Outdated
Don't replace contextually specific errors from check_permissions() with
generic 'Sorry, you are not allowed' error. If check_permissions() returns
a WP_Error, preserve and return it directly. Only use generic error as
fallback when permission check returns false.
@gziolo

Copy link
Copy Markdown
Member

It looks like the changes applied in this PR after the rebase are no longer visible. @Ref34t, is there still anything we should fix in the docs? All the issues raised for the permission checks in the code were fixed during merge to WordPress core, and they are now being synced with #126.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] BugSomething isn't working

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants

@Ref34t@gziolo@jonathanbossenger@felixarntz@justlevine@mokhaled-9h
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Feb 5, 2026. It is now read-only.

docs: Improve permission checking documentation and examples - #95

Open
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean
Open

docs: Improve permission checking documentation and examples#95
Ref34t wants to merge 6 commits into
WordPress:trunkfrom
Ref34t:feature/fix-documentation-clean

Conversation

@Ref34t

@Ref34tRef34t commented Sep 30, 2025

Copy link
Copy Markdown
Contributor

Summary

Changes Made

Documentation Improvements

  • docs/2.getting-started.md: Replaced 22-line manual permission checking example with clean 8-line execute() pattern
  • docs/4.using-abilities.md: Eliminated manual permission checking anti-patterns, added advanced error handling section showing how to handle specific error types

REST API Controller Update

  • includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php: Updated permission check to use strict comparison (true !==) for consistency with execute() pattern

Discussion Point

As @gziolo noted, this isn't a security vulnerability since each ability's execute() method already performs comprehensive permission checking. The REST API controller creates a two-layer permission model:

  1. REST API layer: Pre-check via run_ability_permissions_check()
  2. Ability execution layer: Full check via execute()

The controller's permission callback is architecturally unique because it dynamically checks permissions for any ability. We could consider simplifying by removing the separate permission callback and relying entirely on execute()'s error handling.

Open to feedback on this approach.

Test plan

  • Verify documentation examples follow recommended execute() pattern
  • Confirm no manual permission checking anti-patterns remain in docs
  • Test REST API endpoint permission handling

@github-actions

github-actionsBot commented Sep 30, 2025

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.
Co-authored-by: justlevine <justlevine@git.wordpress.org>
Co-authored-by: Ref34t <mokhaled@git.wordpress.org>
Co-authored-by: felixarntz <flixos90@git.wordpress.org>
Co-authored-by: jonathanbossenger <psykro@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecovBot commented Sep 30, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.20%. Comparing base (9da857e) to head (27b7b1a).

Additional details and impacted files
@@ Coverage Diff @@## trunk #95 +/- ##
============================================
+ Coverage 90.59% 94.20% +3.60% 
============================================
Files 20 7 -13 Lines 1489 328 -1161 Branches 117 116 -1 ============================================
- Hits 1349 309 -1040 + Misses 140 19 -121 
FlagCoverage Δ
javascript94.20% <ø> (ø)
unit?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gziolo

Copy link
Copy Markdown
Member

REST API security vulnerability

I don't think we are really experiencing a security vulnerability here, because every ability handles permissions check individually during execution as discussed in #76 (comment). That being said, we can iterate on the handling because the run controller is unique, as its permissions check model differs for each ability.

@gziologziolo added the [Type] Bug Something isn't working label Sep 30, 2025
@gziolo

Copy link
Copy Markdown
Member

This PR needs a rebase to resolve conflicts after #94 landed. This would help to review the changes proposed in this PR.

@Ref34t

Copy link
Copy Markdown
ContributorAuthor

@gziolo on it

… examples
- Eliminate all manual permission checking examples that could lead to security vulnerabilities
- Add advanced error handling section with specific error code handling
- Update variable names for consistency across examples
- Address @justlevine's feedback about documentation anti-patterns
Change permission check from loose to strict comparison in run_ability_permissions_check().
The previous pattern `if ( ! $ability->check_permission( $input ) )` would incorrectly
pass WP_Error objects as truthy, potentially allowing unauthorized access.
Fixed by using `if ( true !== $ability->check_permission( $input ) )` to properly
handle both false and WP_Error return values.
The strict permission checking now correctly catches input validation
failures in the permission check phase, returning 403 instead of
allowing invalid requests to proceed to execution-specific validation.
Updated 4 failing tests to expect 403 status and appropriate error codes:
- test_resource_ability_requires_get
- test_get_request_with_non_array_input
- test_post_request_with_non_array_input
- test_input_validation_failure_returns_error
@Ref34t
Ref34tforce-pushed the feature/fix-documentation-clean branch from 9df7d76 to bb906c3CompareOctober 1, 2025 08:05
@Ref34tRef34t changed the title Fix documentation anti-patterns and REST API security vulnerabilitydocs: Improve permission checking documentation and examplesOct 1, 2025
@Ref34t

Ref34t commented Oct 1, 2025

Copy link
Copy Markdown
ContributorAuthor

I've updated the PR title and description - you're right that this isn't a security vulnerability.

The reason: even if the REST API permission check had the WP_Error truthiness issue, execute() on line 136 would still block unauthorized access since it performs its own permission check internally.

This update improves code correctness by using strict comparison (true !==) to properly handle all return types from check_permissions(), but it's not fixing an actual security hole due to the two-layer permission model.

Comment threaddocs/4.using-abilities.md Outdated
@jonathanbossenger

Copy link
Copy Markdown
Contributor

I'm in the process of updating documentation in preparation for the 6.9 merge request in #117, so once that's merged, I'll come back here to check what still needs to be included.


$input = $this->get_input_from_request( $request );
if ( ! $ability->check_permissions( $input ) ) {
if ( true !== $ability->check_permissions( $input ) ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sharing relevant feedback that I initially mentioned in WordPress/wordpress-develop#9410 (comment): I don't think we should replace a contextually more specific error (from the actual permission callback) with a generic "Sorry you can't do this" error.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Good catch! I've updated the code to preserve specific error messages from the permission callback instead of replacing them with a generic error.

The permission check now:

  1. Returns the WP_Error directly if check_permissions() returns one (preserves context)
  2. Only falls back to the generic error if it returns false

resolved here https://github.com/WordPress/abilities api/pull/95/commits/27b7b1a8215023b128cbcc55a27f51ac23e8774c

@jonathanbossengerjonathanbossenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some minor documentation suggestions.

Also, the docs folder structure has been updated in #117, so you might have to fetch those updates.

Comment threaddocs/4.using-abilities.md Outdated
Comment threaddocs/4.using-abilities.md Outdated
Don't replace contextually specific errors from check_permissions() with
generic 'Sorry, you are not allowed' error. If check_permissions() returns
a WP_Error, preserve and return it directly. Only use generic error as
fallback when permission check returns false.
@gziolo

Copy link
Copy Markdown
Member

It looks like the changes applied in this PR after the rebase are no longer visible. @Ref34t, is there still anything we should fix in the docs? All the issues raised for the permission checks in the code were fixed during merge to WordPress core, and they are now being synced with #126.

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

[Type] BugSomething isn't working

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

6 participants

@Ref34t@gziolo@jonathanbossenger@felixarntz@justlevine@mokhaled-9h