Install

Works with Drupal: ^11.3 || ^12

Using Composer to manage Drupal site dependencies

Alternative installation files

Download tar.gz 637.7 KB
MD5: 083700c943f290e95b733f979856018c
SHA-1: e6b5fa53a6095d44d220d99651b23edb20d41eb6
SHA-256: 6926a83996e124d8c305a1577f3d9fca4af68dea54d9b3e21f7897c3a7ce352c
Download zip 921.96 KB
MD5: 839510fd471047aac340ba4430fcae6b
SHA-1: 7ec00edae8a112e557e20696ac5108253b6bd439
SHA-256: a22d48f350d7327e337f8f1b1c6a6da3188237fd41a0766c2e1d875c4b108845

Release notes

WARNING: THERE IS NO MIGRATION PATH FROM A RELEASE BEFORE 1.0.0-ALPHA12.

Do not update a site running alpha11 or earlier to this release. Personal Data Vault ships no update hooks, and none is coming until the first beta release. A site on alpha11 or earlier must uninstall the Personal Data Vault modules and install 1.0.0-alpha15 fresh, as the alpha12 release notes describe. From alpha12, alpha13 or alpha14, follow the steps below.

BEFORE YOU UPDATE

  • Drush 13.7 or later. drush pdv:rotate-keys is now a Symfony Console command, which Drush discovers from 13.7. Composer refuses this release next to Drush 13.0 to 13.6: update Drush first. Drupal 11 already needs Drush 13.
  • A secret store on plain http must be allowed. With PDV Vault, the store address must use https, as the vault connection of a consumer site already must. Plain http is accepted for a loopback host, or for a host listed in settings.php, such as a docker service on a trusted network: $settings['pdv_vault.allow_http_hosts'] = ['openbao'];. Without it, every vault read and write fails.
  • Add the new index by hand. A new install creates it; an installed site runs, once: drush php:eval "\Drupal::database()->schema()->addIndex('pdv_subject_key', 'idx__pdv_subject_key__owner', ['owner_uid'], ['fields' => ['owner_uid' => ['type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE]]]);"
  • Then rebuild the cache, and import the French translations of every Personal Data Vault module.

Security: a vault read could undo passphrase protection

Recording vault activity saved the whole subject-key record the request had loaded. When the owner turned passphrase protection on in another request meanwhile, the next read wrote the old record back: the key was protected by the Master KEK alone again. A dormancy reminder and a Master KEK re-wrap could do the same. Each now writes only its own columns, and only while the record is still the one it read; a re-wrap that meets a change is left to the next rotation run (Issue 3620305).

Security: passphrases

  • A new passphrase needs at least 12 characters. The site sets its own minimum under Minimum passphrase length on the Personal Data Vault settings, never below 8 nor above 128, and the passphrase field says it. Passphrases already set keep working.
  • Removing protection checks the current passphrase against the unlock form's limit (flood.unlock_limit), on the same counter: it is no longer a second, unlimited way to guess it.
  • Passphrases are typed into password fields: a screen reader no longer reads them aloud. Browsers may offer to save them again.

(Issue 3620305)

Security: secrets sent in clear

PDV Vault sent the store token and every subject key to its address whatever the scheme; it now requires https, as above. A redirect could also carry a secret elsewhere: a consumer site's calls to the vault and its token request follow no redirect any more, and PDV Vault follows one, as a standby store node sending a request on to the active one, only to an address the same https rule allows (Issue 3620305).

Privacy: a webform submission edited by an administrator

The PDV Webform handler acted on every submission as if it were the current user's own. An administrator editing someone's submission saw it filled from the administrator's own vault, and saving wrote the submitter's answers into it. It now prefills only a new submission, and checks write access and saves back only for the current user's own submission, once it is submitted: a resumed draft keeps what was entered, and saving a draft writes nothing to the vault (Issue 3620309).

Several tenants: requests are handled in their consumer's tenant

A store request from a consumer of another tenant than the default one could not be reviewed, and approving it created the item in the default tenant, where no key opens it. The consent pages now act in the tenant of the request's consumer, through the tenant context. The owner's vault page lists the requests of the tenant it runs in only, and the owner's grant and trust forms offer that tenant's consumers. A store request is declined under the lock its approval takes, so the two cannot interleave (Issue 3620306, Issue 3620310).

Cross-site API

  • Kind access (GET /pdv-api/user/{handle}/kind-access) answered 500 on a locked passphrase vault. It decrypts nothing now.
  • Two record updates at once could lose the fields of one of them. Updates are serialized; the second gets a 409 to retry, now listed in the API contract.
  • Refusals (403, 400, 409, 413, 429) carry Cache-Control: no-store, private and the vault's X-Correlation-ID, as the contract promised.
  • On a consumer site, an unreachable vault no longer turns the unlock return page into a 500.
  • The consent landing answers 400, not 500, to a state longer than 64 URL-safe characters or a return_url longer than 1024.
  • After an unlock, the owner returns to the page they left with its query string, also on a site installed under a base path.

(Issue 3620307, Issue 3620309)

Item kinds in use

A kind in use keeps its type, and is not limited to a tenant that would leave some of its items outside, whatever saves it: its form, a configuration import or code. A kind is also not deleted, or limited to a tenant, while a store request of it waits for approval (Issue 3620309).

Master KEK rotation

  • While the target Master KEK cannot be used, for example because its store is down, a rotation waits instead of setting every subject aside and stopping "incomplete". drush pdv:rotate-keys says so.
  • A target key missing from the key repository broke the status report, the Vault subjects page, the rotation form and drush. The status report now shows it as an unusable Master KEK.

(Issue 3620309)

Settings, ECA and accessibility

  • A dormancy reminder line that is not a whole number of days is refused instead of dropped.
  • An allowed return-URL origin must be scheme://host[:port], with no path, and origins compare in lower case.
  • The ECA owner tokens are filled on read and write events too.
  • The vault page's View link and the file form's current-file link say, to a screen reader, that they open a new tab.
  • A trust given again after a refusal is audited as created, and a change of its expiry as the new pdv.trust.updated.

(Issue 3620305, Issue 3620309)

Faster pages, lighter cron

The owner's vault page decrypts its items with one key unwrap, not one per item: with PDV Vault, one request to the secret store instead of one per item. A file read unwraps once, and the operator's inspection page decrypts nothing, as it says. Measured in the kernel query probe against alpha14: the vault page sends 11 queries instead of 62, the consent approval page 19 instead of 40, a record update 15 instead of 24, a file read 5 instead of 12. The garbage collector reads large tables a range at a time instead of whole on every cron run. Uninstalling the vault or the consumer module now removes what they keep in state and key-value storage, the consumer's vault access tokens included (Issue 3620306, Issue 3620308).

For developers

  • Moved: Drupal\pdv_client\HttpsPolicy is Drupal\pdv\HttpsPolicy; allows() is isAllowedAddress($url, $allow_setting), with PdvVaultConnectionInterface::ALLOW_HTTP_HOSTS_SETTING and VaultTransit::ALLOW_HTTP_HOSTS_SETTING.
  • Replaced: Drupal\pdv_engine\Drush\Commands\RotationCommands by RotateKeysCommand.
  • New: Vault::lockOwnerKind(), Vault::readWithMetadata(), SubjectKeyManager::isMasterKekUsable() and recordDormancyWarning(), MasterKekRotator::isTargetUsable(), ItemKindRepository::countStoreRequests(), countItemsOutsideTenant() and isInUse(), ConsumerContext::setActingConsumerOfRequest(), ConsumerTenantResolver::getTenantIdOfConsumer(), the service pdv_client.http_client, the exceptions SubjectKeyChangedException and RedirectRefusedException, the setting passphrase_min_length.
  • GrantManager::denyRequest() refuses a store request: use StoreRequestManager::denyOffer().
  • Removed, unused: Vault::loadOwnerItems(), SubjectKeyManager::unlockSession(), lockSession(), changePassphrase() and rewrap(), GrantManager::revokeByIdentity(), TrustManager::revokeByIdentity(), HandleStore::getLinkedUsersByConnection().
  • Renamed, verb first: getRecordKinds(), getRecordKindIds(), buildRotationStatus(), buildUnlockPrompt(), buildWriteGrantError(), getHandoffParkStore(), getManageReturnUrl(), submitBatch(), sendTransitRequest(), buildSaveToVaultElement().
  • New constructor arguments: SubjectKeyManager (@database, @cache_tags.invalidator), ConsumerContext (@entity_type.manager), GrantManager and StoreRequestManager (ConsumerContext), GarbageCollector (@keyvalue), PdvRequirements (SubjectKeyManager instead of the key repository and the wrap plugin manager), VaultPassphraseForm (ConsumerFloodGuard), and several forms (ConsumerContext or the tenant context).
Created by: mably
Created on: 21 Oct 2026 at 08:15 UTC
Last updated: 21 Oct 2026 at 08:15 UTC
Bug fixes

Other releases