Summary
The Key Encryption experiment vendors a subset of ericmann/displace-secrets-manager. That repo is now archived and deprecated, superseded by ericmann/secrets-api — the feature plugin proposed for the Secrets API in WordPress 7.2, which adds pluggable platform providers (the main gap raised on the Core proposal).
This came out of the AI team call: pull the new approach into the plugin so the Core merge proposal has a real surface to point at, and tag a release once it lands. I'm happy to take the PR.
What's here today
includes/Vendor/Secrets/ — an SDK subset vendored at commit 49c6aca6beabefc4ed726737d4a88e1baf6869cb, documented in includes/Vendor/Secrets/README.md. Secrets_Bridge stores one secret per connector.
Two things that make this not a drop-in swap
I checked these against the upstream docs and our own code rather than assuming, because the working assumption on the call was that it would be roughly drop-in.
1. Our existing stored keys will not be found automatically.
Secrets_Bridge::secret_key() builds ai/{connector_id}_api_key, so our option rows are _secret_ai/{connector_id}_api_key — namespaced. The new plugin's read-time upgrade deliberately only maps unnamespaced prototype names (docs/migrating-from-displace.md):
Read-time upgrade does not pick those up. The fallback only maps unnamespaced names, so wp_get_secret( 'my-plugin/api_key' ) returns null rather than reaching into a prototype row — the same answer it gives for a secret that was never set.
So a naive swap makes every already-stored connector key read as absent, and users silently re-enter their API keys. Upstream's answer is wp secret migrate-legacy, which we would need to drive ourselves — sites running the AI plugin can't be expected to run a CLI command to keep working.
2. The new API is global functions, so we probably cannot vendor it the way we vendored the old one.
The new plugin exposes wp_get_secret() and friends from src/wp-includes/, shaped to land in Core. secrets-api.php no-ops the entire plugin when the symbol is already defined:
$symbol_taken = function_exists( 'wp_get_secret' );
If we vendored those functions into the AI plugin, we would take the symbol and cause the real feature plugin to disable itself on any site running both. That's worse than the collision the current vendoring was designed to avoid — see "What was intentionally NOT copied" in our vendored README.
Open question this raises
Vendoring the old SDK was justified by not making users install a second plugin. That reasoning doesn't transfer cleanly here. Options, roughly:
- Depend on the feature plugin when present, keep the existing vendored provider as the fallback. Preserves today's zero-install behavior, costs us two code paths.
- Vendor the storage layer only, calling it directly and never defining the global functions.
- Require the feature plugin for the experiment. Cleanest, but changes the install story.
I lean toward the first, but this is the decision worth having opinions on before I write code.
Scope
Summary
The Key Encryption experiment vendors a subset of
ericmann/displace-secrets-manager. That repo is now archived and deprecated, superseded byericmann/secrets-api— the feature plugin proposed for the Secrets API in WordPress 7.2, which adds pluggable platform providers (the main gap raised on the Core proposal).This came out of the AI team call: pull the new approach into the plugin so the Core merge proposal has a real surface to point at, and tag a release once it lands. I'm happy to take the PR.
What's here today
includes/Vendor/Secrets/— an SDK subset vendored at commit49c6aca6beabefc4ed726737d4a88e1baf6869cb, documented inincludes/Vendor/Secrets/README.md.Secrets_Bridgestores one secret per connector.Two things that make this not a drop-in swap
I checked these against the upstream docs and our own code rather than assuming, because the working assumption on the call was that it would be roughly drop-in.
1. Our existing stored keys will not be found automatically.
Secrets_Bridge::secret_key()buildsai/{connector_id}_api_key, so our option rows are_secret_ai/{connector_id}_api_key— namespaced. The new plugin's read-time upgrade deliberately only maps unnamespaced prototype names (docs/migrating-from-displace.md):So a naive swap makes every already-stored connector key read as absent, and users silently re-enter their API keys. Upstream's answer is
wp secret migrate-legacy, which we would need to drive ourselves — sites running the AI plugin can't be expected to run a CLI command to keep working.2. The new API is global functions, so we probably cannot vendor it the way we vendored the old one.
The new plugin exposes
wp_get_secret()and friends fromsrc/wp-includes/, shaped to land in Core.secrets-api.phpno-ops the entire plugin when the symbol is already defined:If we vendored those functions into the AI plugin, we would take the symbol and cause the real feature plugin to disable itself on any site running both. That's worse than the collision the current vendoring was designed to avoid — see "What was intentionally NOT copied" in our vendored README.
Open question this raises
Vendoring the old SDK was justified by not making users install a second plugin. That reasoning doesn't transfer cleanly here. Options, roughly:
I lean toward the first, but this is the decision worth having opinions on before I write code.
Scope
Secrets_Bridgeonto the new API_secret_ai/*rows so no one loses a stored keyincludes/Vendor/Secrets/README.md, or remove it if we stop vendoringdocs/experiments/key-encryption.md, including the threat model if the storage owner changes