Skip to content

Move the Key Encryption experiment onto the new Secrets API feature plugin #1012

Description

@whyisjake

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

  • Decide the integration shape (above)
  • Swap Secrets_Bridge onto the new API
  • Migrate existing _secret_ai/* rows so no one loses a stored key
  • Update includes/Vendor/Secrets/README.md, or remove it if we stop vendoring
  • Update docs/experiments/key-encryption.md, including the threat model if the storage owner changes
  • Tag a release once merged, so the Core proposal can point at it

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions