Trac Watcher: Escape admin output, tighten the props capability, and add tests - #750
Closed
obenland wants to merge 13 commits into
Closed
Trac Watcher: Escape admin output, tighten the props capability, and add tests#750obenland wants to merge 13 commits into
obenland wants to merge 13 commits into
Conversation
…d reports. Prop names, display names and profile URLs were interpolated into the props and message columns, and into most of the reports, without escaping. Escape them on output, and run the prop name through sanitize_text_field() on save. The message column already runs through esc_html() in format_trac_markup(), so the prop is now escaped before it's matched against the message and before it's inserted, keeping the highlighting behaviour unchanged. Also switches the touched sprintf() calls to numbered placeholders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The save and reparse handlers, and the edit affordances in the props column, checked publish_posts. On the make sites o2 Posting Access hands that to every logged-in account that isn't a member of the blog, so it wasn't gating much. Editing props alters attribution on someone else's commit, which feeds the release credits and profile badges, so edit_others_posts is the better fit. Viewing the screen still only needs edit_posts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mporter. - create_tables() used $wpdb without importing it, so activation fatalled. - import_revisions() passed shell_exec()'s return straight to simplexml_load_string(), which is null when the command can't run at all. - The props cloud read $r->id and never set a slug, both of which wp_generate_tag_cloud() expects. - The typos report rendered $c->display_name without selecting the column. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mounts the plugin alongside the other make ones, and defines WP_CORE_LATEST_RELEASE, which its reports page reads unguarded when picking the default version filter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ve handler. Follows the layout the o2 Posting Access suite uses, and runs off the same make wp-env config, so it picks up a matrix entry in the unit tests workflow. Covers both branches that interpolate a stored prop name into the props and message columns, that an ordinary prop is still matched and highlighted after the name is escaped ahead of the comparison, that the name is stripped on the way in on all three write paths, and that the edit affordances track the capability the handler enforces. The handler's rejection paths call die() rather than wp_die(), so a refused request would take the runner down with it. What the capability gates is asserted from the rendering side instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
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 Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
- The list table read properties straight off get_user_by( 'login', $item->author ). The plugin already assumes a revision author may have no account: the committers report LEFT JOINs wp_users and falls back to r.author with ifnull(). The revision and author columns now show the SVN username alone in that case. - The versions-contributed report read $u->user_nicename for any truthy user_id, which no longer resolves once an account is deleted. Falls back to the stored prop name, as the unresolved branch beside it already does. - reports-page.php read WP_CORE_LATEST_RELEASE unguarded, while list-table.php and ui.php both defined()-guard it. An undefined constant is fatal in PHP 8, so the page died outright rather than warning. $version stays null, which add_query_arg(), the version select and the WHERE clause all handle already: the reports come back unfiltered, the same state the blank option in the dropdown produces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the phpcs:ignore annotations with escaping at the point of output: the versions report branches rather than pre-building an anchor, and the two URL columns of the raw report wrap make_clickable() in wp_kses_post() instead of asserting the input was already safe. The versions-contributed case sat a level deeper than its siblings, so every line touched here tripped the indentation sniff. Reindenting it brought the rest of the block into the branch linter's view, hence the remaining fixes: - $versions['2.5'] = $version['2.4'] = 1 assigned to a string offset on the version filter, which is a fatal on the path that gives a 2.4 or 2.5 prop both versions. Both assignments now target $versions. - Spacing, an empty else, and a phpcs:disable for the interpolated table names, which are identifiers from get_svns() and cannot be placeholders. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adopts the pattern the plugin and theme directory environments use: a database-tables.sql of the tables that exist outside WordPress on production, mapped in as env-bin and imported after start. Trac Watcher resolves a prop by GitHub username as a last resort, against a table another part of dotorg owns. The test bootstrap was creating it, so the suite passed while the development site errored on the same lookup. Both databases now get it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds declare( strict_types=1 ) and parameter and return types throughout, and annotates the fixture writes that go straight to the plugin's tables, so the suite is clean under the WordPress standard rather than only the project one. Drops the stub table creation, which the make environment now handles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The range branch matched against $args['revisions'], but display_reports_page() has no $args -- the value it read off the request is in $revisions. Every report loaded with a 1:HEAD or 60000-60100 range emitted an undefined variable warning and then silently dropped the filter, so the report came back unfiltered. The comma-separated branch below it was unaffected and still worked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The raw report takes its gravatar size from the query string. absint() was applied to the value but the default sat behind the null coalesce, so anything that isn't a number -- ?size=abc, ?size=0, ?size=-0 -- reached get_avatar() as 0 and rendered a zero-pixel image. Coalescing to 0 and picking the default up with ?: instead sends unusable input to the same size an absent parameter gets, and still honours a deliberate ?size=8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
setUp() sets the props screen and registers a per_page option on it, both of which are global and outlive the test. Nothing in this suite depends on that leaking, but a suite that runs alongside it would inherit the screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The double space after the opening parenthesis predates this branch, but the branch linter only sniffs lines a change touches, so rewriting the preg_match() argument on that line brought it into scope and failed the build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.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.
An escaping and tidy-up pass over the Trac Watcher admin screens, plus the test coverage the plugin was missing.
Escape output in the list table and reports
The props and message columns, and most of the reports, interpolated stored prop names, display names and profile URLs into HTML unescaped — in several cases alongside a sibling
esc_attr()on the same value in the samesprintf().The message column needed a little care rather than a blanket wrap:
format_trac_markup()has already run the message throughesc_html()by the time the props loop runs, so the prop is now escaped before it is matched against the message and before it is inserted. For ordinary alphanumeric prop namesesc_html()is a no-op, so matching and highlighting are unchanged; names containing markup never matched before either.Prop names also now go through
sanitize_text_field()on save. That only helps new rows, so the output escaping is doing the real work.Require
edit_others_poststo alter props recordsThe save and reparse handlers, and the edit affordances in the props column, checked
publish_posts. On the make sites o2 Posting Access grants that to every logged-in account that isn't a member of the blog, so it wasn't gating much. Editing props rewrites attribution on someone else's commit, which feeds the release credits and profile badges.Viewing the screen still only needs
edit_posts.Fix several PHP warnings
create_tables()used$wpdbwithout importing it, so activation fatalled outright.import_revisions()passedshell_exec()'s return straight tosimplexml_load_string(), which isnullwhenever the command can't run.$r->idand never set a slug, both of whichwp_generate_tag_cloud()expects.$c->display_namewithout selecting the column — theWHEREclause already referenced it.Add a PHPUnit suite
Follows the layout the o2 Posting Access suite uses and runs off the same make wp-env config. Covers both branches that interpolate a stored prop name, that an ordinary prop is still matched and highlighted after the escaping change, that names are stripped on all three write paths, and that the edit affordances track the capability the handler enforces.
The handler's rejection paths call
die()rather thanwp_die(), so a refused request would take the runner down with it; what the capability gates is asserted from the rendering side instead.environments/make/.wp-env.test.jsonpicks up the plugin and definesWP_CORE_LATEST_RELEASE, which the reports page reads unguarded when choosing its default version filter.Testing
sanitize_text_field()(3), and the capability (1). Three tests carry explicit guards against passing vacuously.-1from a direct POST.phpcson the touched files drops from 160 to 118 pre-existing violations; nothing new introduced.🤖 Generated with Claude Code