Repository navigation
Conversation
Without `WP_SECRETS_KEY`, the default keyring derived its key from `LOGGED_IN_KEY` and `LOGGED_IN_SALT`. Rotating the salts, which is routine advice, then made every stored secret unreadable. The constants were also read directly rather than through `wp_salt()`, so a translated placeholder from a localized `wp-config-sample.php`, or a value shared by several of the salts, became the source of the key. There is no fallback any more. Without a usable `WP_SECRETS_KEY`, every call that needs the key returns `secret_key_unavailable`. A key equal to the `wp-config-sample.php` placeholder is not usable, and neither is one equal to any of the authentication keys and salts. An unfilled sample gives all of them the same phrase, so the second check rejects the placeholder in any language. The Site Health key source test now reports a missing or unusable key as recommended, with a newly generated line to add to `wp-config.php`. Follow-up to [64337]. See #66187, #66399.
|
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: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. Props Bot commandsYou can trigger Props Bot actions by commenting on this PR:
|
Test using WordPress PlaygroundThe 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
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
peterwilsoncc
left a comment
There was a problem hiding this comment.
This is testing well.
I've left a couple of notes inline re: i18n and a possible helper. Otherwise it's notes about test suite improvementws.
Resolves the conflicts with r65272: the tests this branch changes now follow the same conventions, with a summary, `@covers` and a failure message for each assertion.
`WP_Secrets_Config_Key_Provider::get_key_source()` returned English text that Site Health showed as is and also searched for "not defined", "not usable" and "legacy interpretation" to choose its result. The text could not be translated without breaking those checks. The provider now reports the state of `WP_SECRETS_KEY` through `get_key_status()`, which returns one of four `KEY_STATUS_*` constants. Site Health uses that, and `get_key_source()` returns translated text.
Now that `WP_SECRETS_KEY` is required, a plugin needs to know whether the Secrets API can be used before moving a credential into it. This returns false when the secrets.php drop-in failed to load, or when the default keyring has no usable key. A provider or keyring from a working drop-in manages its own keys, so it is reported as available. Suggested by peterwilsoncc in review.
Brings in trunk through r65272 and the updated WordPress#15258. The key source test now uses `WP_Secrets_Config_Key_Provider::get_key_status()` rather than the text of the key source, and the Site Health tests this branch adds or changes follow r65272's conventions: a summary, `@covers`, and a failure message for each assertion.
|
Updated:
|
peterwilsoncc
left a comment
There was a problem hiding this comment.
A few minor notes inline.
…te Health. A WP_SECRETS_KEY of only whitespace was accepted as key material. It is now treated like an empty key. Site Health wraps WP_SECRETS_KEY, wp-config.php and wp-config-sample.php in <code>, as the other Site Health results do, and quotes the translated "stop editing" line the way network.php does. From review on PR 15258 by peterwilsoncc.
… the root key. A site that stored secrets and then added or changed WP_SECRETS_KEY without re-wrapping the root key has a usable-looking key, but every read and write fails. wp_is_secrets_api_available() now checks the stored root key, so a caller that checks first keeps its existing storage instead of failing on the write. With no root key stored yet, the API is still available: the first write creates one under the key. See #66187 comment:56.
Without
WP_SECRETS_KEY, the default keyring derived its key fromLOGGED_IN_KEY . LOGGED_IN_SALT. This removes that fallback, as #66187 comment:27 proposes.Two problems with the fallback:
wp_salt(). So a translated placeholder from a localizedwp-config-sample.php, or a value shared by several salts, became the key (comment:29).What changes
WP_SECRETS_KEY, every call that needs the key returnssecret_key_unavailable. That includesWP_SECRETS_KEY_PREVIOUSduring a rotation.wp-config-sample.phpplaceholder is not usable. Neither is one equal to any of the eight keys and salts, nor one that is empty or only whitespace. An unfilled sample gives all of them the same phrase, translated or not, so the second check catches a localized sample without calling__(). A__()check would depend on timing: it returns English beforeload_default_textdomain(), and after that it follows the current user's locale.define()line, to add, or to replace the unusable one with. The "using a fallback key" result is gone. Site Health reads the key's state fromWP_Secrets_Config_Key_Provider::get_key_status()and itsKEY_STATUS_*constants, never from the translatedget_key_source()text.wp_is_secrets_api_available(). A plugin can check it before moving a credential into the Secrets API. It returns false when the drop-in failed to load, when the default keyring has no usable key, or when the key cannot unwrap the stored root key. The last is a site that stored secrets and then added or changed the key without re-wrapping the root key (comment:56).WP_SECRETS_KEYis not defined, not only when the salts are placeholders. As before, it doesn't do this in process-isolated tests.#66399 / #15124 adds the placeholder to
wp-config-sample.phpand hassetup-config.phpfill it in. That PR needs this one first, because only this one rejects the placeholder.#15263 (#66380) is built on this one and should land after it.
Testing
--group secrets: 423 tests on single site and 431 on multisite, no failures (PHP 8.5, trunk at r65378). With trunk's threesrc/files and this PR's tests, there are 21 errors and 7 failures, all in the new tests.WP_SECRETS_KEY_PREVIOUSnot leaking into the current keywp_is_secrets_api_available(), including a key that cannot unwrap the stored root keytest_key_unavailable_is_wp_error_not_nullnow writes underWP_SECRETS_KEY_PREVIOUSinstead of the ambient salts, so it no longer skips on placeholder salts. Neither does any other Secrets API test.A site that stored secrets under the fallback, for example during the beta, can move to a dedicated key. Set
WP_SECRETS_KEYto a new key andWP_SECRETS_KEY_PREVIOUSto the oldLOGGED_IN_KEY . LOGGED_IN_SALT, then re-wrap the root key withWP_Secrets_Key_Manager::rotate_site_key(). The feature plugin'swp secret rotatedoes this. A previous key in that form is hashed exactly as the fallback hashed the salts.Trac ticket: https://core.trac.wordpress.org/ticket/66187
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Drafting the patch, tests and this description, and running the checks above. I reviewed the change and the 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.