Skip to content

Options: Validate transient names (#58903) - #14865

Open
abhishekfdd wants to merge 1 commit into
WordPress:trunkfrom
abhishekfdd:fix/58903-validate-transient-name
Open

abhishekfdd wants to merge 1 commit into
WordPress:trunkfrom
abhishekfdd:fix/58903-validate-transient-name

Conversation

@abhishekfdd

Copy link
Copy Markdown

set_transient() and set_site_transient() accept names that cannot be stored correctly: an empty name writes a bare _transient_ row, non-strings are silently cast, and names longer than the column allows are truncated or fail at the database layer. This PR rejects those names up front.

This picks up the stale #4911 by @jeremyescott and incorporates the review feedback left there and on the ticket.

Changes

  • _wp_is_valid_transient_name( $transient ) (private): returns true only for a string that is not empty or whitespace-only. No casting, so ints, bools, null, arrays and objects are invalid. The name itself is never trimmed or otherwise changed, so existing keys keep working.
  • _wp_is_valid_transient_name_length( $transient, $max_length ) (private): the limit is 172 characters for transients and 167 for site transients. That is option_name VARCHAR(191) minus the _transient_timeout_ (19) and _site_transient_timeout_ (24) prefixes, per comment:21. Site transients get the same limit because they live in wp_options on single site, even though wp_sitemeta.meta_key is VARCHAR(255). The column counts characters, not bytes, so it checks strlen() first and only falls back to mb_strlen() when the byte length is over the limit.
  • set_transient() / set_site_transient(): return false with _doing_it_wrong() for invalid names, before any filters, cache, or option writes. Empty/non-string names and names that are too long get separate messages.
  • get_transient() / get_site_transient(): return false with _doing_it_wrong() for non-string or empty names. The length limit is not enforced here.
  • delete_transient() / delete_site_transient(): unchanged (see open questions).

Why not validate in add_option() or WP_Object_Cache?

On the ticket, dmsnell suggested moving this down into add_option() and WP_Object_Cache::is_valid_key(), and having the transient functions reject whatever those reject. I went with validating in the transient functions instead:

  • add_option() never sees an empty transient name. It receives the prefixed _transient_, which is a valid option name, so the bug gets past it.
  • When an external object cache is in use, transients skip the options table entirely and go through wp_cache_set(). Third-party object cache drop-ins don't share WP_Object_Cache::is_valid_key(), so core can't rely on it.
  • The length limit only applies to transients, because it comes from the timeout prefix. Enforcing it in the options layer would be the wrong place.

Open questions for reviewers

  1. Delete permissiveness. The delete functions accept any name, so sites that already have bad _transient_ / _site_transient_ rows from this bug can still remove them (covered by a test). Should they reject non-strings, or emit a notice for empty names?
  2. _doing_it_wrong() in getters. Getters are called far more often than setters, often with dynamically built names. Is a notice appropriate there, or should they just return false quietly?

Testing

New tests in tests/phpunit/tests/option/transient.php and siteTransient.php cover '', ' ', null, false, true, 0, 1, array() and an object for set and get. They also check that a name exactly at the limit (172 / 167) round-trips through set/get/delete with the timeout row written, that a name one over the limit fails, that a multibyte name at the character limit succeeds (so characters are counted, not bytes), and that delete still works on an empty name.

  • --filter "Tests_Option_Transient|Tests_Option_SiteTransient": OK (58 tests, 185 assertions) on single site and multisite.
  • --group option: no failures on single site (492 tests) or multisite (527 tests). The one warning, in Tests_Option_wpUserSettings, also occurs on trunk.

Both test classes skip under an external object cache, and validation runs before the ext-object-cache branch, so this code path does not depend on the cache.

Trac ticket: https://core.trac.wordpress.org/ticket/58903

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Implementing the code and tests to a design I specified, and drafting this description; I reviewed and directed changes to the diff and test results.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

`set_transient()` and `set_site_transient()` now return false and trigger
`_doing_it_wrong()` when the name is not a non-empty string, or is longer than
172 (or 167 for site transients) characters, which would not fit in the
`option_name` column alongside the timeout prefix. `get_transient()` and
`get_site_transient()` reject non-string and empty names. The delete functions
are left permissive so that existing invalid rows can still be cleaned up.

Introduces the private `_wp_is_valid_transient_name()` and
`_wp_is_valid_transient_name_length()` helpers.

See #58903.
@github-actions

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.

Core Committers: Use this line as a base for the props when committing in SVN:

Props abhishekfdd.

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


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant