From a8009759e7b3bac4e78365b1688870db964da0f3 Mon Sep 17 00:00:00 2001 From: Eric Mann Date: Tue, 10 Nov 2026 06:57:18 -0800 Subject: [PATCH 1/2] Release 0.5.0 src/ follows core trunk through r65378: the root key survives a failed read, secret values and key material stay out of stack traces, a secret name can't end in a newline, and the drop-in can set its overrides as plain variables. The changelog cross-links each ported changeset and the core tickets still in review. --- SECURITY.md | 2 +- docs/changelog/0-5-0.md | 76 +++++++++++++++++++++++++++++++++++++++++ docs/spec/scope.md | 2 +- secrets-api.php | 4 +-- 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 docs/changelog/0-5-0.md diff --git a/SECURITY.md b/SECURITY.md index 7bab642..60d0616 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -25,7 +25,7 @@ explanation rather than silence. ## Scope -This is a pre-release feature plugin (`0.4.0`). The API is in WordPress core trunk for 7.2, and +This is a pre-release feature plugin (`0.5.0`). The API is in WordPress core trunk for 7.2, and `src/` follows it. There is no supported-version matrix yet: `main` is the only supported branch, and fixes land there. diff --git a/docs/changelog/0-5-0.md b/docs/changelog/0-5-0.md new file mode 100644 index 0000000..884e7c9 --- /dev/null +++ b/docs/changelog/0-5-0.md @@ -0,0 +1,76 @@ +--- +title: "0.5.0" +description: "src/ follows core trunk through r65378: the root key survives a failed read, secret values stay out of stack traces, names can't end in a newline, and the drop-in can set plain variables." +date: 2026-11-10 +--- + +# 0.5.0 + +Released 2026-11-10. In this release `src/` and the shared test helpers match WordPress core trunk +as of [r65378](https://core.trac.wordpress.org/changeset/65378), byte for byte. + +## Ported from core + +| Changeset | Change | Props | +|---|---|---| +| [r65272](https://core.trac.wordpress.org/changeset/65272) | The tests follow core's PHPUnit guidelines: a summary, `@covers` and a message for each assertion. The docblocks no longer refer to the feature plugin. | peterwilsoncc | +| [r65373](https://core.trac.wordpress.org/changeset/65373) | A failed read of the root key no longer replaces it, which made every stored secret unreadable. Only a write creates a root key. [#66413](https://core.trac.wordpress.org/ticket/66413) | aryanjasala, dhrupo, ericmann | +| [r65375](https://core.trac.wordpress.org/changeset/65375) | A secret name can't end in a newline. Secret values and key material are marked `#[\SensitiveParameter]`, so they stay out of stack traces. [#66414](https://core.trac.wordpress.org/ticket/66414) | dhrupo, aryanjasala, ericmann | +| [r65377](https://core.trac.wordpress.org/changeset/65377) | Summaries, `@covers` and messages for r65375's tests. | – | +| [r65378](https://core.trac.wordpress.org/changeset/65378) | The `secrets.php` drop-in can set its overrides as plain variables, such as `$wp_secrets_keyring = new My_Keyring();`. The plugin's own drop-in loader does the same. | ericmann, jorbin | + +peterwilsoncc committed r65272, and whyisjake committed r65373 through r65378. + +Two changes in those changesets are not in the plugin, because they edit core admin files the plugin +can't change. WordPress 7.2 has both: + +- [r65374](https://core.trac.wordpress.org/changeset/65374) lists `secrets.php` under Plugins > + Drop-ins and in Site Health Info. Props nofearinc, ericmann. +- r65375 masks `_wp_secrets_root_key` on Settings > All Settings, where saving it changed made + every stored secret unreadable. Don't edit that field on WordPress 7.1 or earlier. + +## Changed + +- **Drop-ins should mark their own sensitive parameters.** Attributes aren't inherited, so a + keyring's `wrap()` and a provider's `set()` add `#[\SensitiveParameter]` themselves. The examples + and [`drop-in-example.php`](../reference/drop-in-example.php) now do. On PHP 7.4 the attribute + line is a comment. +- **[ADR 0017](../decisions/0017-abstract-classes-for-the-extension-points.md):** the provider, + store and keyring become abstract classes instead of interfaces, so a later release can add a + method without breaking every drop-in. The plugin ports it once core commits + [PR 15744](https://github.com/WordPress/wordpress-develop/pull/15744). + +## Coming in core: `WP_SECRETS_KEY` becomes required + +As in 0.4.0, this release still derives the key from `LOGGED_IN_KEY` and `LOGGED_IN_SALT` when +`WP_SECRETS_KEY` is not defined. [PR 15258](https://github.com/WordPress/wordpress-develop/pull/15258) +removes that fallback in core ([ADR 0016](../decisions/0016-require-wp-secrets-key.md)), and has +been tested and reviewed. It also adds `wp_is_secrets_api_available()`, which returns false while +there is no usable key, or while the key can't unlock the stored secrets. + +Define `WP_SECRETS_KEY` now. On a site that already stores secrets under the fallback, move them +first, or they become unreadable. The steps from 0.4.0 still apply: + +1. Set `WP_SECRETS_KEY` to a new value, from `wp secret generate-key`. +2. Set `WP_SECRETS_KEY_PREVIOUS` to the old `LOGGED_IN_KEY . LOGGED_IN_SALT`, the two values + joined together. +3. Run `wp secret rotate`. +4. Remove `WP_SECRETS_KEY_PREVIOUS`. + +## In review in core + +Each of these will be ported once committed. + +| Ticket | Change | PR | +|---|---|---| +| [#66187](https://core.trac.wordpress.org/ticket/66187) | Require `WP_SECRETS_KEY`. Reject the placeholder, a key equal to a key or salt, and an empty or whitespace-only key. Add `wp_is_secrets_api_available()`. | [15258](https://github.com/WordPress/wordpress-develop/pull/15258) | +| [#66399](https://core.trac.wordpress.org/ticket/66399) | `setup-config.php` writes `WP_SECRETS_KEY`, and `wp-config-sample.php` has a placeholder for it. | [15124](https://github.com/WordPress/wordpress-develop/pull/15124) | +| [#66380](https://core.trac.wordpress.org/ticket/66380) | Site Health reports a broken backend, and stops giving advice that locks stored secrets. | [15263](https://github.com/WordPress/wordpress-develop/pull/15263) | +| [#66187](https://core.trac.wordpress.org/ticket/66187) | The Site Health checks move into `WP_Site_Health` and `WP_Debug_Data`. | [15881](https://github.com/WordPress/wordpress-develop/pull/15881) | +| [#66187](https://core.trac.wordpress.org/ticket/66187) | The provider, store and keyring become abstract classes ([ADR 0017](../decisions/0017-abstract-classes-for-the-extension-points.md)). | [15744](https://github.com/WordPress/wordpress-develop/pull/15744) | +| [#66424](https://core.trac.wordpress.org/ticket/66424) | A failed read of a secret's record no longer reads as a missing secret. | [15329](https://github.com/WordPress/wordpress-develop/pull/15329) | + +WP-CLI's `wp config create` will generate the key as well +([wp-cli/config-command#235](https://github.com/wp-cli/config-command/pull/235)). Moving the +`wp secret` commands into WP-CLI is proposed in +[wp-cli/entity-command#677](https://github.com/wp-cli/entity-command/issues/677). diff --git a/docs/spec/scope.md b/docs/spec/scope.md index a420d78..9fddb59 100644 --- a/docs/spec/scope.md +++ b/docs/spec/scope.md @@ -15,7 +15,7 @@ scope", and "Timeline" in the [proposal][proposal]. ## As built -**The plugin.** `secrets-api.php` declares version `0.4.0`, `Requires at least: 6.8`, and +**The plugin.** `secrets-api.php` declares version `0.5.0`, `Requires at least: 6.8`, and `Requires PHP: 7.4`. **Core-bound versus plugin-only.** Everything under `src/` is written to be copied into diff --git a/secrets-api.php b/secrets-api.php index e6d1257..16306e5 100644 --- a/secrets-api.php +++ b/secrets-api.php @@ -3,7 +3,7 @@ * Plugin Name: Secrets API * Plugin URI: https://github.com/ericmann/secrets-api * Description: Feature plugin for the WordPress Secrets API proposed for 7.2. Encrypted, versioned credential storage with pluggable storage and keyring back ends. - * Version: 0.4.0 + * Version: 0.5.0 * Requires at least: 6.8 * Requires PHP: 7.4 * License: GPL-2.0-or-later @@ -25,7 +25,7 @@ /** * Plugin version. */ -define( 'WP_SECRETS_API_PLUGIN_VERSION', '0.4.0' ); +define( 'WP_SECRETS_API_PLUGIN_VERSION', '0.5.0' ); /** * The WordPress version expected to ship the Secrets API in core. From 7d6b0bf4432b723c181b32d440e65aa48aabf3a7 Mon Sep 17 00:00:00 2001 From: Eric Mann Date: Tue, 10 Nov 2026 08:08:36 -0800 Subject: [PATCH 2/2] Rewrite the 0.5.0 changelog for the two breaking changes 0.5.0 now carries r65385 and r65390, so the changelog opens with what to do before updating: define WP_SECRETS_KEY, moving secrets stored under the salt fallback first, and change a drop-in's `implements` to `extends`, since the old form is a fatal error. The ported table runs through r65390, and r65389 joins the core-only changes. The in-review table is down to 15881 and 15329. --- docs/changelog/0-5-0.md | 87 +++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/docs/changelog/0-5-0.md b/docs/changelog/0-5-0.md index 884e7c9..5123c3c 100644 --- a/docs/changelog/0-5-0.md +++ b/docs/changelog/0-5-0.md @@ -1,13 +1,48 @@ --- title: "0.5.0" -description: "src/ follows core trunk through r65378: the root key survives a failed read, secret values stay out of stack traces, names can't end in a newline, and the drop-in can set plain variables." +description: "WP_SECRETS_KEY is required, the provider, store and keyring are abstract classes, and src/ follows core trunk through 7.2 beta 5. Two breaking changes: check both before updating." date: 2026-11-10 --- # 0.5.0 Released 2026-11-10. In this release `src/` and the shared test helpers match WordPress core trunk -as of [r65378](https://core.trac.wordpress.org/changeset/65378), byte for byte. +as of [r65390](https://core.trac.wordpress.org/changeset/65390), byte for byte: what ships +in 7.2 beta 5. + +## Before you update + +This release has two breaking changes. Both match core from 7.2 beta 5. + +**1. `WP_SECRETS_KEY` is required.** Without it, the Secrets API is unavailable: every call that +needs the key returns `secret_key_unavailable`. Until now the key was derived from `LOGGED_IN_KEY` +and `LOGGED_IN_SALT` when `WP_SECRETS_KEY` was not defined. Rotating the salts then locked every +stored secret ([ADR 0016](../decisions/0016-require-wp-secrets-key.md)). + +- A site that already defines `WP_SECRETS_KEY` needs to do nothing. +- A site with no stored secrets only needs to add a key. Site Health shows a generated `define()` + line to paste into `wp-config.php`, and so does `wp secret generate-key`. +- A site that stored secrets under the fallback must move them **before** updating, or they read + as `secret_key_unavailable` until it does: + 1. Set `WP_SECRETS_KEY` to a new value, from `wp secret generate-key`. + 2. Set `WP_SECRETS_KEY_PREVIOUS` to the old `LOGGED_IN_KEY . LOGGED_IN_SALT`, the two values + joined together. + 3. Run `wp secret rotate`. + 4. Remove `WP_SECRETS_KEY_PREVIOUS`. + + Nothing is lost if you update first: the root key is left as it was, and the same steps make + the secrets readable again afterward. + +**2. A `secrets.php` drop-in uses `extends`, not `implements`.** `WP_Secrets_Provider`, +`WP_Secrets_Store` and `WP_Secrets_Keyring` are abstract classes now, not interfaces, so a later +release can add a method without breaking every drop-in +([ADR 0017](../decisions/0017-abstract-classes-for-the-extension-points.md)). Change `implements` +to `extends` in your drop-in. Nothing else in the contract changes. + +A drop-in that still uses `implements` is **a fatal error on every request**, WP-CLI included. It +does not fail closed: PHP reports it while compiling the drop-in, before WordPress can catch it. +If you copied one of the [examples](../reference/examples.md), take the new version. They extend +the classes. ## Ported from core @@ -18,44 +53,32 @@ as of [r65378](https://core.trac.wordpress.org/changeset/65378), byte for byte. | [r65375](https://core.trac.wordpress.org/changeset/65375) | A secret name can't end in a newline. Secret values and key material are marked `#[\SensitiveParameter]`, so they stay out of stack traces. [#66414](https://core.trac.wordpress.org/ticket/66414) | dhrupo, aryanjasala, ericmann | | [r65377](https://core.trac.wordpress.org/changeset/65377) | Summaries, `@covers` and messages for r65375's tests. | – | | [r65378](https://core.trac.wordpress.org/changeset/65378) | The `secrets.php` drop-in can set its overrides as plain variables, such as `$wp_secrets_keyring = new My_Keyring();`. The plugin's own drop-in loader does the same. | ericmann, jorbin | +| [r65385](https://core.trac.wordpress.org/changeset/65385) | `WP_SECRETS_KEY` is required. A key that is empty or only whitespace, the sample placeholder, or equal to a key or salt is not usable. New: `wp_is_secrets_api_available()` and `WP_Secrets_Config_Key_Provider::get_key_status()`. | ericmann, peterwilsoncc, Clorith, dhrupo, wildworks, johnbillion, jorbin, digitalpritam | +| [r65387](https://core.trac.wordpress.org/changeset/65387) | Site Health reports a key that can't unlock the stored secrets, and secrets that can't be listed, as problems instead of good. It gives advice that fits a provider drop-in and a legacy-form key. [#66380](https://core.trac.wordpress.org/ticket/66380) | nofearinc, ericmann, ravipatel, dhrupo, Clorith, jorbin, peterwilsoncc | +| [r65390](https://core.trac.wordpress.org/changeset/65390) | The provider, store and keyring are abstract classes. | ericmann, jorbin | -peterwilsoncc committed r65272, and whyisjake committed r65373 through r65378. +peterwilsoncc committed r65272, and whyisjake committed the rest. -Two changes in those changesets are not in the plugin, because they edit core admin files the plugin -can't change. WordPress 7.2 has both: +Three changes from the same period are in core only, because they edit core files the plugin can't +change. WordPress 7.2 has them: - [r65374](https://core.trac.wordpress.org/changeset/65374) lists `secrets.php` under Plugins > Drop-ins and in Site Health Info. Props nofearinc, ericmann. - r65375 masks `_wp_secrets_root_key` on Settings > All Settings, where saving it changed made - every stored secret unreadable. Don't edit that field on WordPress 7.1 or earlier. + every stored secret unreadable. On WordPress 7.1 or earlier, don't edit that field. +- [r65389](https://core.trac.wordpress.org/changeset/65389) has the installer write + `WP_SECRETS_KEY` into `wp-config.php`, with a placeholder in `wp-config-sample.php`. + [#66399](https://core.trac.wordpress.org/ticket/66399). Props ericmann, peterwilsoncc, + digitalpritam, ravipatel, Clorith, dhrupo. ## Changed - **Drop-ins should mark their own sensitive parameters.** Attributes aren't inherited, so a - keyring's `wrap()` and a provider's `set()` add `#[\SensitiveParameter]` themselves. The examples - and [`drop-in-example.php`](../reference/drop-in-example.php) now do. On PHP 7.4 the attribute - line is a comment. -- **[ADR 0017](../decisions/0017-abstract-classes-for-the-extension-points.md):** the provider, - store and keyring become abstract classes instead of interfaces, so a later release can add a - method without breaking every drop-in. The plugin ports it once core commits - [PR 15744](https://github.com/WordPress/wordpress-develop/pull/15744). - -## Coming in core: `WP_SECRETS_KEY` becomes required - -As in 0.4.0, this release still derives the key from `LOGGED_IN_KEY` and `LOGGED_IN_SALT` when -`WP_SECRETS_KEY` is not defined. [PR 15258](https://github.com/WordPress/wordpress-develop/pull/15258) -removes that fallback in core ([ADR 0016](../decisions/0016-require-wp-secrets-key.md)), and has -been tested and reviewed. It also adds `wp_is_secrets_api_available()`, which returns false while -there is no usable key, or while the key can't unlock the stored secrets. - -Define `WP_SECRETS_KEY` now. On a site that already stores secrets under the fallback, move them -first, or they become unreadable. The steps from 0.4.0 still apply: - -1. Set `WP_SECRETS_KEY` to a new value, from `wp secret generate-key`. -2. Set `WP_SECRETS_KEY_PREVIOUS` to the old `LOGGED_IN_KEY . LOGGED_IN_SALT`, the two values - joined together. -3. Run `wp secret rotate`. -4. Remove `WP_SECRETS_KEY_PREVIOUS`. + keyring's `wrap()` and a provider's `set()` add `#[\SensitiveParameter]` themselves. The + examples and [`drop-in-example.php`](../reference/drop-in-example.php) do. On PHP 7.4 the + attribute line is a comment. +- **CI tests the plugin where it runs.** The full suite runs on WordPress 7.1 and the 6.8 floor. + On trunk, which ships the API, a separate job checks only that the plugin stands down. ## In review in core @@ -63,11 +86,7 @@ Each of these will be ported once committed. | Ticket | Change | PR | |---|---|---| -| [#66187](https://core.trac.wordpress.org/ticket/66187) | Require `WP_SECRETS_KEY`. Reject the placeholder, a key equal to a key or salt, and an empty or whitespace-only key. Add `wp_is_secrets_api_available()`. | [15258](https://github.com/WordPress/wordpress-develop/pull/15258) | -| [#66399](https://core.trac.wordpress.org/ticket/66399) | `setup-config.php` writes `WP_SECRETS_KEY`, and `wp-config-sample.php` has a placeholder for it. | [15124](https://github.com/WordPress/wordpress-develop/pull/15124) | -| [#66380](https://core.trac.wordpress.org/ticket/66380) | Site Health reports a broken backend, and stops giving advice that locks stored secrets. | [15263](https://github.com/WordPress/wordpress-develop/pull/15263) | | [#66187](https://core.trac.wordpress.org/ticket/66187) | The Site Health checks move into `WP_Site_Health` and `WP_Debug_Data`. | [15881](https://github.com/WordPress/wordpress-develop/pull/15881) | -| [#66187](https://core.trac.wordpress.org/ticket/66187) | The provider, store and keyring become abstract classes ([ADR 0017](../decisions/0017-abstract-classes-for-the-extension-points.md)). | [15744](https://github.com/WordPress/wordpress-develop/pull/15744) | | [#66424](https://core.trac.wordpress.org/ticket/66424) | A failed read of a secret's record no longer reads as a missing secret. | [15329](https://github.com/WordPress/wordpress-develop/pull/15329) | WP-CLI's `wp config create` will generate the key as well