ADR-020: Master key request-lifetime caching
Table of contents
Status
Accepted
Date
2026-03-28
Context
Master key providers re-read the key material from disk, environment variables, or re-derived it via HKDF on every decrypt operation. In requests that decrypt multiple secrets (e.g., frontend rendering with several vault-backed content elements), this caused repeated filesystem reads or HKDF computations for the same key material.
The master key does not change within a single HTTP request, so repeated derivation is pure overhead.
Decision
Cache the derived master key in memory for the lifetime of the current request:
- On first access, the master key provider reads/derives the key and stores it in a request-lifetime cache slot.
- Subsequent decrypt operations within the same request reuse the cached key without additional I/O or derivation.
- The cache lives in the shared
Abstractbase class, keyed by concrete provider class, so each provider keeps an isolated slot andMaster Key Provider clearon one provider never wipes another's.Cached Key () - All providers expose a static
clear(declared onCached Key () Master) that wipes the cached key material viaKey Provider Interface sodium_.memzero ()
This follows the principle of minimizing key material exposure: the key exists in memory only for the duration of the request and is actively cleared rather than left for garbage collection.
Wipe lifecycle differs by provider
The cache is wiped by two distinct mechanisms depending on the provider:
- FileMasterKeyProvider / EnvironmentMasterKeyProvider define a
__that callsdestruct () clear. When the provider instance is garbage-collected (typically at end of request), the cached key is zeroed.Cached Key () - Typo3MasterKeyProvider (the default) deliberately has no
__. Its cache slot is shared across instances, so wiping on the first instance's destruction would break the rest of the request. For this provider the cache is zeroed only by an explicitdestruct () clearcall or implicitly when PHP frees statics at script shutdown. Long-running processes (scheduler tasks, daemons) that must observe a rotated TYPO3Cached Key () encryptionshould callKey clearexplicitly.Cached Key ()
Consequences
Positive
- One key derivation per request instead of per-decrypt, eliminating redundant I/O and HKDF computations.
- Secure cleanup:
sodium_wipes key material — viamemzero () __for the File/Env providers and via an explicitdestruct () clearfor the default Typo3 provider — so it does not persist in memory beyond the request (at the latest, until PHP frees statics at shutdown for the Typo3 provider).Cached Key () - Transparent: Read-path callers are unaware of the caching; the
cache-wipe seam is now uniform via
clearon the interface.Cached Key ()
Negative
- Memory residency: The master key remains in process memory for the
full request duration rather than being immediately discarded after each
use. For the default Typo3 provider, residency is "until an explicit
clearcall or PHP shutdown" because it has no destructor.Cached Key () - Lifecycle dependency: File/Env providers rely on PHP object lifecycle
(
__) for cleanup; the default Typo3 provider relies on an explicitdestruct clear. Long-running processes (e.g., workers, scheduler tasks) must callCached Key () clearto bound residency and to observe a rotated source key.Cached Key ()