Release 0.11.0 - #152
Merged
Merged
Conversation
Add .wp-env.json to standardize local development environment with: - PHP 7.4 (plugin minimum) - Latest WordPress (auto-updates) - Query Monitor auto-installed - Debug constants enabled - Test environment with PHP 8.4 + WordPress trunk 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Update minimum supported WordPress version from 5.9 to 6.4. Changes: - Plugin header: Add 'Requires at least: 6.4' - README.md: Update minimum WP to 6.4, tested up to 6.8 - .phpcs.xml.dist: Update minimum_supported_wp_version to 6.4 - Workflow: Update test matrix minimum from 5.9 to 6.4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Removes unnecessary eclint settings, explicit indent_size for tabs, and LICENSE section. This aligns with the standard WordPress core .editorconfig configuration used across other plugins.
Replace SVN-based test setup with @wordpress/env. Changes: - Use wp-env to set up WordPress test environment - Update matrix: 'latest' → '6.8', 'trunk' → 'master' - Rename composer scripts: test/test-ms → test-integration/test-integration-ms - Remove SVN dependency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Normalize and expand on items like sorting packages, and script descriptions.
Comprehensive standardisation to align with other Automattic plugins: - Renames integrations.yml to integration.yml for consistent naming - Adds Dependabot configuration for automated dependency updates - Updates composer.json to use yoast/wp-test-utils instead of phpunit-polyfills - Renames test-integration to test:integration for script naming consistency - Pins all GitHub Actions to specific commit SHAs to prevent supply chain attacks - Adds explicit permissions blocks (least privilege) - Adds persist-credentials: false to checkout actions - Fixes template injection vulnerabilities using environment variables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates .gitignore with section headers and adds .wp-env.override.json exclusion. Updates .distignore with consistent patterns matching other plugins in the organisation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Composer scripts use colons (test:integration) not hyphens. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates the requirements on [rector/rector](https://github.com/rectorphp/rector) to permit the latest version. - [Release notes](https://github.com/rectorphp/rector/releases) - [Commits](rectorphp/rector@1.2.0...2.2.14) --- updated-dependencies: - dependency-name: rector/rector dependency-version: 2.2.14 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com>
Move reviewer configuration from dependabot.yml to CODEOWNERS file per GitHub's recommended approach. This allows the same reviewers to be automatically assigned for all PRs, not just Dependabot ones. See: https://github.com/dependabot/codeowner-migration-action 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updates CI workflow to test against WP 6.4 and master with PHP 7.4 and latest. Removes allowed_failure flag so CI fails if tests fail on any supported configuration. Updates README to reflect tested versions (WP 6.9). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](lodash/lodash@4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6.0.1 to 6.0.2 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@8e8c483...de0fac2) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] <support@github.com>
…ions-e6ee9d7de3
Aligns the test infrastructure with the a8c-plugins standard by using yoast/wp-test-utils bootstrap pattern with conditional WordPress loading. The bootstrap now only loads WordPress when the integration testsuite is requested, and tests are relocated from tests/unit/ to tests/Integration/ to reflect their true nature as integration tests requiring a full WordPress environment.
Migrate test infrastructure to wp-test-utils
Move agent instructions from .claude/CLAUDE.md to a root-level AGENTS.md with five standardised sections: project knowledge, commands, conventions, architectural decisions, and common pitfalls. The .claude/CLAUDE.md now references AGENTS.md so that Claude Code sessions still pick up the content automatically.
Add AGENTS.md with structured agent guidance
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](isaacs/minimatch@v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the actions group with 2 updates: [shivammathur/setup-php](https://github.com/shivammathur/setup-php) and [ramsey/composer-install](https://github.com/ramsey/composer-install). Updates `shivammathur/setup-php` from 2.36.0 to 2.37.0 - [Release notes](https://github.com/shivammathur/setup-php/releases) - [Commits](shivammathur/setup-php@44454db...accd612) Updates `ramsey/composer-install` from 3.1.1 to 4.0.0 - [Release notes](https://github.com/ramsey/composer-install/releases) - [Commits](ramsey/composer-install@3cf229d...65e4f84) --- updated-dependencies: - dependency-name: shivammathur/setup-php dependency-version: 2.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: ramsey/composer-install dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] <support@github.com>
…ions-eb2f49cd38
Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](micromatch/picomatch@2.3.1...2.3.2) --- updated-dependencies: - dependency-name: picomatch dependency-version: 2.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](lodash/lodash@4.17.23...4.18.1) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.18.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com>
Three small XSS surfaces existed on the zone admin screen, each requiring an attacker who could already create a post or craft a URL clicked by an editor. Closing them so the admin UI is safe regardless of what an upstream filter, importer, or another plugin writes into post titles, post status, or the error query parameter. The autocomplete result renderer concatenated raw post fields into HTML; switching to jQuery .text() ensures values are inserted as text nodes. The recent-posts AJAX response now escapes the title/status fragment before it is appended into a <select>, so titles introduced via paths that bypass title sanitisation can no longer break out. The admin error notice now resolves a sanitised error code through the existing zone_messages whitelist, with a fallback to the generic error, instead of echoing arbitrary URL-supplied text.
Zone "details" are stored by serialising an array into the term description and read back via maybe_unserialize on every render of the admin screen and on every REST or feed response that lists zones. That makes the description column a high-traffic deserialisation sink: if anything (a buggy companion plugin, a database compromise, a future filter on zoninator_insert_zone) writes a crafted payload, the next read instantiates whatever PHP objects it names, which is the standard shape of a POP-gadget RCE. Replace maybe_unserialize with an explicit is_serialized check followed by unserialize() with allowed_classes=false, and reject anything that does not deserialise to an array. Strings are still treated as plain text via wp_parse_args, so legitimate descriptions are unchanged. While here, require a logged-in user to list zones via the REST index. The endpoint previously accepted unauthenticated requests and exposed zone names and descriptions to any caller, which is unnecessary since the admin UI is the only first-party consumer. The historical behaviour is preserved behind the zoninator_rest_get_zones_permissions_check filter so anyone integrating against the public listing can opt back in deliberately.
A handful of unrelated bugs spotted during a security review of the plugin. None are exploitable on their own, but each is the sort of latent fault that turns into a real incident later. ajax_recent_posts ran the zone post query before checking the nonce and capability. The data was never written to the response without a valid check, so this is not a disclosure today, but moving the checks to the top closes the gap and avoids paying the DB cost for unauthorised calls. lock_zone referenced $user->ID outside the branch that defined $user, so calling lock_zone( $zone, $user_id ) with an explicit user id would have emitted an undefined-variable notice and stored the wrong value (or nothing, on PHP 8). Use get_current_user_id() and store $user_id directly so both call shapes work. The Delete confirmation string was wrapped in esc_js( 'text', 'zoninator' ) — esc_js takes one argument, so the text-domain string was discarded and the message was never marked translatable. Wrap it in __() and pass a single argument to esc_js. The REST update_zone description ran strip_tags after the field's sanitize_string callback had already entity-encoded the input, so it had nothing to strip. Drop the redundant callback to keep the layered sanitisation honest.
The wp_ajax_zoninator_search_posts handler was reachable by any authenticated user and returned title, ID, date, type and status for posts in publish or future status. Because the search uses WP_Query's 's' parameter, low-privileged users could brute-force scheduled post titles character by character via a search oracle, exposing embargoed content. Bring the handler into line with every other Zoninator AJAX endpoint by calling verify_nonce and verify_access at the top of the method. The default verify_access path checks edit_others_posts, the same cap that gates the Zones admin UI. The autocomplete on the Zones page now sends _wpnonce alongside the existing search request so the legitimate editor flow keeps working. Adds an integration regression test covering subscriber rejection, admin-without-nonce rejection and the valid editor success path.
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
Cuts the 0.11.0 release. The work itself has already landed on
developover the past few months across more than twenty PRs; this branch simply rolls those into a release with the three standard commits — changelog, regenerated POT, version bumps.The headline of the release is the security work that landed in PRs #148, #149, #150 and #151. Three reflected/stored XSS surfaces on the zone admin screen are now closed, the term-description deserialiser uses
unserializewithallowed_classes => falseso a poisoned description cannot trigger PHP object instantiation, and thezoninator_search_postsAJAX handler now requires both an authentication check and a valid nonce so a logged-in subscriber can no longer enumerate scheduled-post titles. Alongside that, a handful of latent bugs around the lock heartbeat and the Delete confirm i18n string are tidied up.The release also raises the minimum WordPress version from 5.9 to 6.4, in line with the rest of our plugin standards. The PHP minimum stays at 7.4.
The one behaviour change worth flagging in upgrade notes is that
GET /wp-json/zoninator/v1/zonesnow requires an authenticated user. This previously responded to anonymous callers, which leaked zone names and descriptions to anyone who knew the endpoint existed. The admin UI does not consume this endpoint (it goes through admin-ajax), so that path is unaffected. Sites that genuinely depend on anonymous zone listing — for instance, a static site generator pulling zone metadata at build time — can opt back into the historical behaviour with a one-line filter:The remainder of the release is the maintenance and dev-environment work that has been accumulating since 0.10.2: migration to
wp-env, thewp-test-utilsswap, GitHub Actions hardening, CODEOWNERS migration, and the usual run of Dependabot updates.Six smoke tests against a live wp-env confirmed each of the security fixes behaves as intended, and one i18n test confirmed the previously-discarded Delete confirm string is now extractable. CI is green on develop ahead of this release branch.
Test plan
CHANGELOG.mdcovers everything that landed since 0.10.2 and matches the existing format.zoninator.php(header +ZONINATOR_VERSIONconstant),package.json,package-lock.json,README.md(Stable tag), and the regeneratedlanguages/zoninator.pot(Project-Id-Versionheader).Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/zoninatorand now contains the new "Are you sure you want to delete this zone?" string.0.11.0frommain, push the tag, and confirm the GitHub Release + WordPress.org SVN deploy workflows fire.