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.
…ealth. Two Site Health tests reported a backend whose secrets cannot be listed as healthy: `wp_secrets_site_health_find_undecryptable()` and `wp_secrets_site_health_count_needing_rotation()` turned the `WP_Error` from `wp_list_secrets()` into an empty result, which read as "All secrets can be decrypted" and "No secrets are pending rotation". The key source test also detected a broken `secrets.php` drop-in by looking for "broken" in the keyring's description, which is a translated string, so on translated sites a broken drop-in was reported as using a dedicated key. Return the `WP_Error` from both helpers and report it, and detect the broken keyring by its class. Follow-up to [64337]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s the provider. A `secrets.php` drop-in can replace the whole secrets provider, as a host's KMS or vault integration does. Such a provider protects secrets with its own keys, but the key source Site Health test still described the unused default keyring, and recommended defining `WP_SECRETS_KEY`, which that provider never reads. Report the provider's label instead, with a good status. Follow-up to [64337]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The key source Site Health test recommended defining `WP_SECRETS_KEY`, or regenerating it, "with a value from `wp secret generate-key`". That command is not part of core or WP-CLI. Following the advice after any secret was stored also makes every stored secret unreadable and blocks saving new ones: the root key stays wrapped with the previous site key, and core has no path that re-wraps it. The key source test then reported the new key as good. * Report a configured key that cannot unwrap the stored root key as critical, and say that restoring the previous value fixes it. * Recommend a dedicated key only before any secret is stored. Once secrets exist, warn that changing the key or the salts it derives from makes them unreadable. * Drop the reference to the WP-CLI command. * Say in the undecryptable secrets test that restoring the previous key makes the secrets readable again, instead of that there is no way to recover them. Follow-up to [64337]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tored secrets. Addresses review. Only the default keyring reads `wp-config.php`, so the critical key source result names `WP_SECRETS_KEY` only for it. A keyring from a `secrets.php` drop-in, such as a key service that is unreachable or was installed without re-wrapping the root key, has its own error shown instead. The label no longer says the key "does not match", which does not fit an outage. With `WP_SECRETS_KEY` now required, the key source and undecryptable results no longer mention `LOGGED_IN_KEY` and `LOGGED_IN_SALT`. When a drop-in replaces the provider, Site Health Info marks the keyring class and key source as not used, instead of describing a keyring that the site does not use. See #66380, #66187.
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.
…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.
…lts. Wraps WP_SECRETS_KEY and secrets.php in <code>, as the other Site Health results do and as PR 15258 now does for the strings it adds. The provider label and the keyring's error message are escaped on their own. From review on PR 15258 by peterwilsoncc.
…P_Debug_Data`. The Secrets API's Site Health checks lived in their own file, secrets-site-health.php, as functions hooked onto site_status_tests and debug_information. That was how the feature plugin added them. Core's own checks are methods of WP_Site_Health, registered in get_tests(), and core's debug information comes from WP_Debug_Data. - The three checks are now WP_Site_Health::get_test_secrets_api_key_source(), get_test_secrets_api_undecryptable() and get_test_secrets_api_needs_rotation(), registered as direct tests. Their helpers are private methods. - The debug information is WP_Debug_Data::get_wp_secrets_api(), under the wp-secrets-api key, as core's other sections are prefixed wp-. - The results use the blue Security badge and the actions key, like the other checks. - secrets-site-health.php and its wp_secrets_site_health_*() functions are removed. None of them has been in a release. Follow-up to r64337. See #66187.
|
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. |
# Conflicts: # src/wp-admin/includes/secrets-site-health.php # tests/phpunit/tests/secrets/siteHealth.php
| $fields = array( | ||
| 'dropin_active' => array( | ||
| 'label' => __( 'Drop-in active' ), | ||
| 'value' => wp_using_secrets_dropin() ? __( 'Yes' ) : __( 'No' ), |
There was a problem hiding this comment.
Maybe for another issue, but most of the core methods provide an untranslated 'debug' key, which is used inside WP_Debug_Data::format() when copying debug info for support.
| 'value' => wp_using_secrets_dropin() ? __( 'Yes' ) : __( 'No' ), | |
| 'value' => wp_using_secrets_dropin() ? __( 'Yes' ) : __( 'No' ), | |
| 'debug' => wp_using_secrets_dropin(), |
Moves the Secrets API's Site Health checks into
WP_Site_HealthandWP_Debug_Data, as @peterwilsoncc suggested in review on #15258. Since [64337] they lived insrc/wp-admin/includes/secrets-site-health.php, as functions hooked ontosite_status_testsanddebug_information. That's how the feature plugin added them, and the file was carried over as it was.#15258 and #15263 are now in trunk as [65385] and [65387], and this is updated on top of them, so the diff is only the move.
What changes
WP_Site_Healthmethods:get_test_secrets_api_key_source(),get_test_secrets_api_undecryptable()andget_test_secrets_api_needs_rotation(). They're registered as direct tests inget_tests(), with the same IDs and labels as before. Their helpers are private methods.WP_Debug_Data::get_wp_secrets_api(). Its key is nowwp-secrets-apiinstead ofsecrets-api, because core's own sections all start withwp-. The fields are unchanged.actionskey.secrets-site-health.phpis removed, along with itsrequire_onceinclass-wp-site-health.phpand thewp_secrets_site_health_*()functions. None of them has been in a release.The code inside each check is unchanged. The diff is the move,
$this->calls in place of the old functions, and the result helper.Testing
--group secrets: 431 tests on single site and 439 on multisite, no failures.--group site-health: 121 tests, no failures, so core's own Site Health tests still pass.tests/phpunit/tests/secrets/siteHealth.phpcalls the new methods. The debug section is reached through reflection, aswpDebugDataPlugins.phpdoes. The test thatwp_secrets_site_health_tests()kept tests registered earlier is gone with the filter. The registration test now checksWP_Site_Health::get_tests().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: Moving the code and updating the tests, running the checks above, and drafting this description. 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.