Skip to content

Undecryptable credential passes as a valid password #3501

Description

@chubes4

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.

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

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions