Summary

The pdv_client module stores each linked user's opaque vault handle in users.data in plaintext (HandleStore, under the vault_handle:CONNECTION name). Every vault read is handle-addressed, and handles are only minted through the owner's consent ceremony, so the handle store is effectively the second factor alongside the cached OAuth token. A read of the consumer site's database today yields both the handles and the access token, which is enough to replay every linked user's handle and read their vault data within the consumer's grants. The vault-side property "a stolen handle is useless without the OAuth secret" does not help here, because on the consumer site the token is present.

Proposed change

Add an optional per-connection encryption Key (a key module Key, mirroring the existing client-secret key) and have HandleStore AEAD-encrypt handles at rest under a key derived from it. With the wrapping key held outside the database (an environment, file, or secrets-manager key provider), a database dump alone yields a short-lived token but no usable handles, and a token with no handles reads nothing.

Design

  • Self-contained crypto (libsodium XChaCha20-Poly1305); no dependency on the main pdv module, since pdv_client runs on a site with no vault of its own.
  • Per-connection key derived with HKDF-SHA256, domain-separated by connection id; the additional authenticated data binds each ciphertext to its (connection, uid) so a stored row cannot be lifted to another user or connection.
  • Opt-in: with no key configured the handle is stored as today. Fail-closed on write (a configured but unreadable key raises an error at consent rather than silently storing plaintext); on read, an undecryptable row is treated as "not linked", so the user simply re-consents.

Scope and trade-offs

This is defense-in-depth against an at-rest or database-dump compromise of the consumer site, not an active hole: it takes a full database read to matter, and exposure is already bounded by token lifetime and the owner's revocable grants. The cached OAuth access token in State is the companion exposure and is worth moving out of plaintext State for the same reason; the handle store is the higher-leverage asset because reads are handle-addressed.

Issue fork pdv-3594165

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

  • mably committed 16c316a7 on 1.x
    feat: #3594165 pdv_client: encrypt vault handles at rest with an out-of-...
mably’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.