Problem
When a stored secret cannot be decrypted, decrypt_fields() deliberately leaves the envelope in place:
// On decryption failure, leave the encrypted blob as-is
// so it doesn't silently become an empty string.
That is a reasonable choice on its own, but nothing downstream distinguishes an envelope from a plaintext secret. is_authenticated() only checks that the value is non-empty:
public function is_authenticated(): bool {
$credentials = $this->credentials();
return '' !== $credentials['username'] && '' !== $credentials['password'];
}
An undecryptable blob is non-empty, so the provider reports itself healthy and the literal string dm:enc:v1:<base64>:<base64>:<base64> is sent to the remote service as the password.
Observed impact
On a site where the derived key no longer matched the stored ciphertext, four providers (mission_control, superset, buildkite, wporg_trac) all reported as authenticated and every request returned HTTP 401 from the remote service. The failure presented as a remote outage rather than a local key problem, and diagnosis required manually decrypting the stored values to discover the real cause.
The secret material was intact the whole time. Only the key was wrong.
Expected
A value that still carries the dm:enc: prefix after decrypt_fields() is a failed decryption, not a credential. It should not reach an outbound request.
Suggested handling:
- Treat a remaining
dm:enc: prefix as unauthenticated in is_authenticated().
- Have
resolve_auth_ref() return an explicit error identifying the failure as undecryptable rather than unconfigured, so the operator knows the stored value exists but the key changed.
- Log the decryption failure through the existing
log_encryption_error() path with the provider slug and field name, never the value.
Verification
Store a secret, change wp_salt('auth'), then confirm the provider reports unauthenticated with a decryption-specific reason and that no request is attempted. A freshly encrypted secret must continue to round-trip and authenticate normally.
AI Assistance
Diagnosed by Claude Sonnet 4.6 in Claude Code while investigating repeated HTTP 401 responses from Mission Control. The model inspected BaseAuthProvider, compared stored ciphertext against database backups, and confirmed with a round-trip through the provider's own encrypt_value/decrypt_value that the cipher implementation was working and only the derived key differed. Chris Huber directed the work and reviewed this report.
Problem
When a stored secret cannot be decrypted,
decrypt_fields()deliberately leaves the envelope in place:That is a reasonable choice on its own, but nothing downstream distinguishes an envelope from a plaintext secret.
is_authenticated()only checks that the value is non-empty:An undecryptable blob is non-empty, so the provider reports itself healthy and the literal string
dm:enc:v1:<base64>:<base64>:<base64>is sent to the remote service as the password.Observed impact
On a site where the derived key no longer matched the stored ciphertext, four providers (
mission_control,superset,buildkite,wporg_trac) all reported as authenticated and every request returnedHTTP 401from the remote service. The failure presented as a remote outage rather than a local key problem, and diagnosis required manually decrypting the stored values to discover the real cause.The secret material was intact the whole time. Only the key was wrong.
Expected
A value that still carries the
dm:enc:prefix afterdecrypt_fields()is a failed decryption, not a credential. It should not reach an outbound request.Suggested handling:
dm:enc:prefix as unauthenticated inis_authenticated().resolve_auth_ref()return an explicit error identifying the failure as undecryptable rather than unconfigured, so the operator knows the stored value exists but the key changed.log_encryption_error()path with the provider slug and field name, never the value.Verification
Store a secret, change
wp_salt('auth'), then confirm the provider reports unauthenticated with a decryption-specific reason and that no request is attempted. A freshly encrypted secret must continue to round-trip and authenticate normally.AI Assistance
Diagnosed by Claude Sonnet 4.6 in Claude Code while investigating repeated HTTP 401 responses from Mission Control. The model inspected
BaseAuthProvider, compared stored ciphertext against database backups, and confirmed with a round-trip through the provider's ownencrypt_value/decrypt_valuethat the cipher implementation was working and only the derived key differed. Chris Huber directed the work and reviewed this report.