Skip to content

Port r65373 through r65390 from core - #20

Merged
ericmann merged 11 commits into
mainfrom
core-r65378
Nov 10, 2026
Merged

ericmann merged 11 commits into
mainfrom
core-r65378

Conversation

@ericmann

@ericmann ericmann commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Ports seven changesets from core trunk, so src/ matches trunk through r65390, which is 7.2 beta 5. One commit per changeset, each with its props.

Changeset Change Props
r65373 (#66413) A failed read of the root key no longer replaces it. Only a write creates a root key. aryanjasala, dhrupo, ericmann
r65375 (#66414) Secret names can't end in a newline (\z). Secret values and key material are #[\SensitiveParameter]. dhrupo, aryanjasala, ericmann
r65377 Summaries, @covers and messages for r65375's tests. –
r65378 The drop-in can set its overrides as plain variables. Ported to wp_secrets_api_load_dropin(). ericmann, jorbin
r65385 WP_SECRETS_KEY is required. The salt fallback is gone (ADR 0016). wp_is_secrets_api_available() and get_key_status() are new. ericmann, peterwilsoncc, Clorith, dhrupo, wildworks, johnbillion, jorbin, digitalpritam
r65387 (#66380) Site Health reports a broken setup as broken, and gives advice that fits the site. nofearinc, ericmann, ravipatel, dhrupo, Clorith, jorbin, peterwilsoncc
r65390 The provider, store and keyring are abstract classes (ADR 0017). A drop-in changes implements to extends. One that doesn't is a fatal error, not a fail-closed API. ericmann, jorbin

Not ported, because they change core admin files the plugin can't touch, and core 7.2 has them:

  • r65374 lists secrets.php in _get_dropins(), which has no filter.
  • The part of r65375 that masks _wp_secrets_root_key on Settings > All Settings, in wp-admin/options.php.
  • r65389 (#66399) has setup-config.php write WP_SECRETS_KEY, with a placeholder in wp-config-sample.php.

Beyond the straight port:

  • Lint config. r65373's docblocks open paragraphs with a function name, which WordPress-Docs rejects; core's ruleset doesn't include WordPress-Docs, so that sniff is excluded for src/. Its direct $wpdb writes join the PHPStan ignores that already cover the option store (stub types stricter than core's). src/ stays byte for byte with trunk.
  • Example drop-ins. Attributes aren't inherited, so the three examples and docs/reference/drop-in-example.php mark their own wrap() and set() parameters.
  • Spec. envelope-encryption.md and providers-and-keyrings.md describe the new root-key write, the plain-variable globals and the attribute.
  • r65385 in tests. tests/bootstrap.php defines the fixed test key, as core's test bootstrap now does, because a test library from before 7.2 doesn't. Three plugin-only WP-CLI tests that run isolated define the key themselves. get_auth_constant_values() joins the PHPStan ignores: it calls constant() twice, so level 10 can't narrow the value.
  • Spec and reference. envelope-encryption.md and host-managed-keys.md no longer describe the fallback, and providers-and-keyrings.md says what Site Health shows for a provider drop-in.
  • r65390 beyond src/. The bootstrap requires the renamed files, and the fallback store, the three examples, drop-in-example.php and host-managed-keys.md extend the classes. The specs, README and smoke test wording follow, and ADR 0017 records that implements is a fatal. The smoke test records that fatal as a new case.
  • CI: trunk installs core from wordpress-develop. The stand-down job took core from the nightly zip and the tests from wordpress-develop trunk. After r65390, the tests' mocks extended classes that the day-old nightly still declared as interfaces, and the bootstrap died. bin/install-wp-tests.sh now installs core's src/ for trunk from the same tarball as the tests. nightly still means the nightly zip.
  • docs/reference/ is regenerated.

Testing

Run locally against WordPress 7.1.4, PHP 8.5:

  • Single site: 563 tests, 35 skipped. Multisite: 563 tests, 24 skipped. main has 509.
  • PHPCS, the PHP 7.4 compatibility check and PHPStan level 10 are clean. make reference-check passes.
  • The WP-CLI smoke test passes, 148 of 148.
  • After each commit, src/ was compared with trunk at the matching changeset, and is identical.
  • Not run locally: the examples suite (needs Moto and Vault). CI runs it.

Props aryanjasala, dhrupo, jorbin, peterwilsoncc, Clorith, wildworks, johnbillion, digitalpritam, nofearinc, ravipatel.

Ports r65373 from core trunk. WP_Secrets_Key_Manager::get_root_key() generated
a new root key whenever get_site_option() returned false, which also happens
when the query fails. On a single site the new key then overwrote the stored
one, and every existing secret became unreadable. A false result is now checked
against the database before a key is generated, and a failed read returns
WP_Error without writing anything. Reading a secret no longer creates a root
key. The first root key is inserted with INSERT IGNORE on a single site; on
multisite every request uses the oldest row, and a request that lost the race
deletes its own.

https://core.trac.wordpress.org/changeset/65373
https://core.trac.wordpress.org/ticket/66413

Props aryanjasala, dhrupo, ericmann.
Ports r65375 from core trunk. wp_secrets_validate_name() accepted a name with a
trailing newline, because $ also matches before a final newline; the pattern
now ends with \z. The parameters that receive a secret value or key material are
marked #[\SensitiveParameter], so they stay out of stack traces. Attributes are
not inherited, so keyrings and providers in a drop-in need to add it
themselves.

The third fix in r65375 masks _wp_secrets_root_key on Settings > All Settings.
It changes wp-admin/options.php, which the plugin cannot, so it is not ported.

https://core.trac.wordpress.org/changeset/65375
https://core.trac.wordpress.org/ticket/66414

Props dhrupo, aryanjasala, ericmann.
Ports r65377 from core trunk, which gives the tests added in r65375 a summary,
@Covers and a message for each assertion, as the other Secrets API tests have
since r65272.

https://core.trac.wordpress.org/changeset/65377
https://core.trac.wordpress.org/ticket/66414
Ports r65378 from core trunk to the plugin's drop-in loader. The drop-in is
required inside wp_secrets_api_load_dropin(), so a plain assignment such as
$wp_secrets_keyring = new My_Keyring(); set a local variable, and the
override was ignored without a word. The loader now declares the three
overrides global before the require, as core's wp_load_secrets_dropin() does
and as require_wp_db() does for db.php. Assigning through $GLOBALS, as the
examples do, works as before.

https://core.trac.wordpress.org/changeset/65378
https://core.trac.wordpress.org/ticket/66187

Props ericmann, jorbin.
src/ stays byte for byte with core trunk, so two rules from this repository's
stricter setup are relaxed for the code r65373 added, as for earlier ports:

- PHPCS: WordPress-Docs rejects a docblock paragraph that opens with a function
  name. Core's ruleset does not include WordPress-Docs, so the sniff is
  excluded for src/.
- PHPStan: the WordPress stubs type $wpdb->sitemeta as string|null and want a
  literal-string for wpdb::prepare(). The root key's direct writes are added to
  the ignores that already cover the option store.
r65375 marks the provider's set() $value and the keyring's wrap()
$key_material with #[\SensitiveParameter]. Attributes are not inherited, so the
three example drop-ins and docs/reference/drop-in-example.php add it to their
own implementations.

The spec pages describe the ported behavior: only a write creates the root
key, a failed read of it no longer leads to a new one (r65373), the drop-in can
set its overrides as plain variables (r65378), and implementations add the
attribute themselves (r65375).
Ports r65385 from core trunk, the change ADR 0016 decided. Without
WP_SECRETS_KEY, the default keyring derived its key from LOGGED_IN_KEY .
LOGGED_IN_SALT, so rotating the salts locked every stored secret, and an
unfilled or translated sample placeholder could become the key. The fallback is
gone: without a usable WP_SECRETS_KEY, every call that needs the key returns
secret_key_unavailable. A key is not usable when it is empty or only
whitespace, the wp-config-sample.php placeholder, or equal to any of the
authentication keys and salts. Site Health shows a generated define() line, and
reads the key's state from the new get_key_status(). wp_is_secrets_api_available()
reports whether the API can be used, including whether the key can unwrap the
stored root key.

Beyond the straight port:

- tests/bootstrap.php defines the fixed test key, as core's test bootstrap now
  does, because a test library from before 7.2 doesn't.
- Three plugin-only WP-CLI tests that run isolated define the key themselves.
- get_auth_constant_values() joins the PHPStan ignores: it calls constant()
  twice, so level 10 can't narrow the value. Core's level doesn't report it.
- docs/spec/envelope-encryption.md and docs/reference/host-managed-keys.md no
  longer describe the fallback.

A site that stored secrets under the fallback keeps them. Set WP_SECRETS_KEY to
a new key and WP_SECRETS_KEY_PREVIOUS to the old LOGGED_IN_KEY . LOGGED_IN_SALT,
then run wp secret rotate.

https://core.trac.wordpress.org/changeset/65385
https://core.trac.wordpress.org/ticket/66187

Props ericmann, peterwilsoncc, Clorith, dhrupo, wildworks, johnbillion, jorbin, digitalpritam.
Ports r65387 from core trunk. A key that cannot unwrap the stored root key is
now critical instead of good. For the default keyring the result says
WP_SECRETS_KEY was changed or removed, and for a drop-in keyring it shows that
keyring's own error. When the secrets cannot be listed, the undecryptable test
is critical and the rotation test is recommended, instead of both reading the
WP_Error as an empty list. A broken keyring is detected by its class, not by
matching a translated word. When a drop-in replaces the provider, the key
source test says the provider manages its own keys, and Site Health Info marks
the keyring class and key source as not used. A key that is not in the
recommended form is no longer described as one to regenerate once secrets are
stored, and the undecryptable result no longer says the values can never be
recovered.

docs/spec/providers-and-keyrings.md describes what Site Health shows for a
provider drop-in.

https://core.trac.wordpress.org/changeset/65387
https://core.trac.wordpress.org/ticket/66380

Props nofearinc, ericmann, ravipatel, dhrupo, Clorith, jorbin, peterwilsoncc.
@ericmann ericmann changed the title Port r65373, r65375, r65377 and r65378 from core Port r65373 through r65387 from core Nov 10, 2026
Ports r65390 from core trunk (ADR 0017). WP_Secrets_Provider, WP_Secrets_Store
and WP_Secrets_Keyring become abstract classes instead of interfaces, so a later
release can add a method with a default implementation without breaking every
drop-in. Their files move from interface-*.php to class-*.php.

A drop-in changes `implements` to `extends`. One that still uses `implements`
is a fatal error on every request, WP-CLI included, not a fail-closed API: PHP
reports it while compiling the drop-in, before the try/catch around the
require can run.

Beyond the straight port:

- The plugin's bootstrap requires the renamed files, and its drop-in loader's
  docblock describes the classes and the new fatal.
- Secrets_API_Prototype_Fallback_Store, the three examples,
  docs/reference/drop-in-example.php and host-managed-keys.md extend the
  classes.
- The spec pages, the README, the examples' READMEs and specs, and the smoke
  test's wording follow. The smoke test records a drop-in that still uses
  `implements` as a fatal, alongside the existing incomplete-class case.
- ADR 0017 records that the old form is a fatal, not a fail-closed error.

https://core.trac.wordpress.org/changeset/65390
https://core.trac.wordpress.org/ticket/66187

Props ericmann, jorbin.
The stand-down job paired core from the nightly zip with a test suite from
wordpress-develop trunk. The nightly is built about once a day, so after
15744 the suite's mocks extended classes the nightly still declared as
interfaces, and the bootstrap died. trunk now installs core's src/ from the
same tarball as the test suite, as core runs its own tests. nightly still
means the nightly zip.

The smoke test's new implements case checks only for "Fatal error", because
the nightly smoke leg can run on a nightly from before the classes, where the
same drop-in is a different fatal.
@ericmann ericmann changed the title Port r65373 through r65387 from core Port r65373 through r65390 from core Nov 10, 2026
@ericmann
ericmann merged commit f841709 into main Nov 10, 2026
15 checks passed
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