Repository navigation
fix(abilities): enforce per-object post capabilities in upsert-post - #3933
Merged
Merged
Conversation
The upsert-post ability was gated only by the chat action. Authorize the requested write against WordPress object capabilities, bounded by the acting principal and any agent capability ceiling: - inserts require the post type's create_posts; publish, future and private (and parent stub creation) require publish_posts - updates require edit_post on the resolved target (by ID, slug or identity), delete_post when trashing - a different post_author or protected meta requires edit_others_posts - taxonomy assignment requires assign_terms; new terms need edit_terms Add PermissionHelper::can_wp_capability() for principal-aware checks of primitive and meta capabilities. Also harden two sibling chat-gated abilities: - write-self-memory honors memory file editability, so protected files such as site-wide rules cannot be written through it - sign-pending-action-resolution only signs actions inside the caller's pending-action scope
The test sets the protected `_source` identity key, which the tightened upsert-post policy reserves for principals holding edit_others_posts. Run it as an editor (still non-admin) so it keeps proving the acting user becomes the shell author; author denial of protected meta stays covered by UpsertPostPermissionTest.
chubes4
added a commit
that referenced
this pull request
Nov 14, 2026
… tests (#3942) * test(events): use relative future dates in EventUpsertTitleNoiseTest Hardcoded 2026-11-12..17 dates fell into the past and tripped the upsert past-event guard. Closes #3941. * test(events): clear privacy-policy page option in upsert test setup Recycled test post IDs can land on 3, the default privacy policy page, so core requires manage_privacy_options (super admin on multisite) to edit it and the per-object upsert-post check from #3933 denies the site-admin test user. Closes #3944. --------- Co-authored-by: homeboy-ci[bot] <266378653+homeboy-ci[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to a security advisory.
datamachine/upsert-postwas authorized by the chat action alone. Its execute path then wrote whatever post type, status, author, target post, meta and terms the caller supplied, without any WordPress object capability checks. This PR authorizes each write against WordPress capabilities for the acting principal.datamachine/upsert-postcreate_posts.publish,futureandprivatealso needpublish_posts, and so does creating parent stubs, because stubs are published. Unregistered post types are limited to principals withmanage_options.edit_poston the target, whether it comes frompost_id, slug or identity meta. The check runs in the permission callback and again at write time against the post that was actually resolved. Only a shell allocated by the request's own identity reservation skips it. A status change to publish still needspublish_posts, and trashing needsdelete_post.post_authorother than the acting user needsedit_others_posts. Protected (underscore-prefixed)meta_inputkeys and theidentity_metakey need it too.assign_termson the taxonomy. Unknown terms are only created when the caller hasedit_terms.upsert_post_forbidden(HTTP 403).PermissionHelper::can_wp_capability()New principal-aware helper for primitive and meta capabilities. It uses the same trusted contexts as
can(): WP-CLI subject todatamachine_cli_bypass_permissions, Action Scheduler queue runs, and system context.can(),manage_optionsis not treated as a blanket grant. WordPress capability mapping decides.Sibling chat-gated abilities reviewed
write-self-memoryfilewith no editability check. Protected or capability-gated memory files, such as the shared site rules file, could be written.MemoryFileRegistry::is_editable()contract as the regular memory write path. The check uses the same filename normalization as the store, so variant spellings can't get around it.sign-pending-action-resolutionPendingActionScope). Managers keep operator-wide reach.create-agentconfigonly affects the caller's own agent, and its tool execution stays within the owner's capabilities.submit/get/retry/cancel-delegated-operationauthorizecallback runs on every phase and receives the actor.Compatibility
Tests
UpsertPostPermissionTest(SQLite-safe), covering:UpsertPostAbilityTestnow runs as an administrator.write-self-memoryUpsertPostAbilityTest|UpsertPostPermissionTest|PermissionHelperTest|MultiAgentScoping|SelfServiceAgentCreation): 77/77.UpsertPostAbilityTestnormally runs only on MySQL, which wasn't available locally. Forced onto SQLite, it showed the same 20 SQLite-only failures on this branch as onmain. CI will run it on MySQL.email-multi-inbox-forward-smokefails identically onmain. All others pass.AI assistance
This PR was written by an AI agent (Claude) via Extra Chill Bot.