Skip to content

Fix cache keys and eviction of the cipher key store decorators - #207

Merged
DavidBadura merged 2 commits into
1.24.xfrom
fix-cache-store-decorator-keys
Sep 23, 2026
Merged

DavidBadura merged 2 commits into
1.24.xfrom
fix-cache-store-decorator-keys

Conversation

@DavidBadura

Copy link
Copy Markdown
Member

The Psr6CacheStoreDecorator and Psr16CacheStoreDecorator used cache keys like subjectId:... and id:.... The colon is reserved in PSR-6 and PSR-16, so both decorators threw an InvalidArgumentException with symfony/cache. The keys are now hashed, which also avoids problems with reserved characters or long ids. A cache hit is only accepted if the cached key matches the requested id or subject, so hash collisions can't return a wrong key.

Removal was incomplete as well. removeWithSubjectId() only evicted the subject entry, but decryption goes through get($id), so the removed keys stayed usable until the cache expired, which defeats crypto shredding. remove() had the same problem the other way around. The decorators now keep a list of cached key ids per subject and evict all related entries.

The tests now run against a real symfony/cache adapter, which is added as a dev dependency.

The cache keys used a colon, which is a reserved character in PSR-6 and
PSR-16, so the decorators failed with any compliant cache like symfony/cache.
Ids and subject ids are now hashed, and a cache hit is only accepted if the
cached key really belongs to the requested id or subject.

Removing keys also left entries behind: removeWithSubjectId() kept the
cached keys by id and remove() kept the cached key by subject, so deleted
personal data could still be decrypted until the cache expired. The
decorators now remember the cached key ids per subject and evict all of
them.
@DavidBadura DavidBadura added the bug Something isn't working label Sep 23, 2026
@DavidBadura DavidBadura added this to the 1.24.2 milestone Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Hello 👋

here is the most recent benchmark result:

StackHydratorBench
==================

+-------------------------------+-------------------+------------------+-----------------+------------+
|                               | time (kde mode)                      | memory                       |
+-------------------------------+-------------------+------------------+-----------------+------------+
| subject                       | Tag: <current>    | Tag: base        | Tag: <current>  | Tag: base  |
+-------------------------------+-------------------+------------------+-----------------+------------+
| benchHydrate1Object ()        | 4.400μs (±0.00%)  | 3.000μs (±0.00%) | 3.245mb         | 3.253mb    |
| benchExtract1Object ()        | 58.200μs (±0.00%) | 3.600μs (±0.00%) | 3.245mb         | 3.253mb    |
| benchHydrate1000Objects ()    | 2.113ms (±0.00%)  | 2.098ms (±0.00%) | 3.476mb         | 3.481mb    |
| benchExtract1000Objects ()    | 1.891ms (±0.00%)  | 1.847ms (±0.00%) | 3.399mb         | 3.404mb    |
| benchHydrate1000000Objects () | 1.202s (±0.00%)   | 1.189s (±0.00%)  | 3.476mb         | 3.018mb    |
| benchExtract1000000Objects () | 1.098s (±0.00%)   | 1.118s (±0.00%)  | 2.941mb         | 2.941mb    |
+-------------------------------+-------------------+------------------+-----------------+------------+

HydratorWithLazyBench
=====================

+------------------------------------------+--------------------+--------------------+-----------------+------------+
|                                          | time (kde mode)                         | memory                       |
+------------------------------------------+--------------------+--------------------+-----------------+------------+
| subject                                  | Tag: <current>     | Tag: base          | Tag: <current>  | Tag: base  |
+------------------------------------------+--------------------+--------------------+-----------------+------------+
| benchHydrate1Object ()                   | 0.600μs (±0.00%)   | 0.400μs (±0.00%)   | 2.771mb         | 2.803mb    |
| benchHydrate1ObjectTriggerInit ()        | 3.800μs (±0.00%)   | 3.400μs (±0.00%)   | 2.771mb         | 2.803mb    |
| benchHydrate1000Objects ()               | 351.667μs (±0.00%) | 365.000μs (±0.00%) | 2.810mb         | 2.810mb    |
| benchHydrate1000ObjectsTriggerInit ()    | 2.921ms (±0.00%)   | 2.804ms (±0.00%)   | 2.934mb         | 2.813mb    |
| benchHydrate1000000Objects ()            | 221.544ms (±0.00%) | 248.226ms (±0.00%) | 2.810mb         | 2.810mb    |
| benchHydrate1000000ObjectsTriggerInit () | 1.821s (±0.00%)    | 2.051s (±0.00%)    | 2.934mb         | 2.812mb    |
+------------------------------------------+--------------------+--------------------+-----------------+------------+

StackHydratorWithCryptographyBench
==================================

+-------------------------------+-------------------+------------------+-----------------+------------+
|                               | time (kde mode)                      | memory                       |
+-------------------------------+-------------------+------------------+-----------------+------------+
| subject                       | Tag: <current>    | Tag: base        | Tag: <current>  | Tag: base  |
+-------------------------------+-------------------+------------------+-----------------+------------+
| benchHydrate1Object ()        | 10.000μs (±0.00%) | 4.200μs (±0.00%) | 2.798mb         | 2.797mb    |
| benchExtract1Object ()        | 4.200μs (±0.00%)  | 4.400μs (±0.00%) | 2.798mb         | 2.797mb    |
| benchHydrate1000Objects ()    | 3.135ms (±0.00%)  | 2.950ms (±0.00%) | 3.088mb         | 2.977mb    |
| benchExtract1000Objects ()    | 2.712ms (±0.00%)  | 2.704ms (±0.00%) | 2.977mb         | 2.977mb    |
| benchHydrate1000000Objects () | 1.771s (±0.00%)   | 1.790s (±0.00%)  | 3.089mb         | 3.088mb    |
| benchExtract1000000Objects () | 1.717s (±0.00%)   | 1.698s (±0.00%)  | 2.974mb         | 2.974mb    |
+-------------------------------+-------------------+------------------+-----------------+------------+

HydratorBench
=============

+-------------------------------+------------------+------------------+-----------------+------------+
|                               | time (kde mode)                     | memory                       |
+-------------------------------+------------------+------------------+-----------------+------------+
| subject                       | Tag: <current>   | Tag: base        | Tag: <current>  | Tag: base  |
+-------------------------------+------------------+------------------+-----------------+------------+
| benchHydrate1Object ()        | 3.400μs (±0.00%) | 3.000μs (±0.00%) | 2.771mb         | 2.803mb    |
| benchExtract1Object ()        | 3.400μs (±0.00%) | 3.200μs (±0.00%) | 2.771mb         | 2.803mb    |
| benchHydrate1000Objects ()    | 2.232ms (±0.00%) | 2.101ms (±0.00%) | 2.936mb         | 2.936mb    |
| benchExtract1000Objects ()    | 1.933ms (±0.00%) | 1.888ms (±0.00%) | 2.839mb         | 2.838mb    |
| benchHydrate1000000Objects () | 1.173s (±0.00%)  | 1.140s (±0.00%)  | 2.936mb         | 2.936mb    |
| benchExtract1000000Objects () | 1.063s (±0.00%)  | 1.254s (±0.00%)  | 2.839mb         | 2.838mb    |
+-------------------------------+------------------+------------------+-----------------+------------+

HydratorWithCryptographyBench
=============================

+-------------------------------+-------------------+-------------------+-----------------+------------+
|                               | time (kde mode)                       | memory                       |
+-------------------------------+-------------------+-------------------+-----------------+------------+
| subject                       | Tag: <current>    | Tag: base         | Tag: <current>  | Tag: base  |
+-------------------------------+-------------------+-------------------+-----------------+------------+
| benchHydrate1Object ()        | 93.200μs (±0.00%) | 75.400μs (±0.00%) | 2.888mb         | 2.887mb    |
| benchExtract1Object ()        | 9.200μs (±0.00%)  | 8.200μs (±0.00%)  | 2.803mb         | 2.798mb    |
| benchHydrate1000Objects ()    | 3.756ms (±0.00%)  | 3.823ms (±0.00%)  | 3.082mb         | 3.082mb    |
| benchExtract1000Objects ()    | 4.919ms (±0.00%)  | 4.854ms (±0.00%)  | 2.984mb         | 2.984mb    |
| benchHydrate1000000Objects () | 2.261s (±0.00%)   | 2.339s (±0.00%)   | 3.082mb         | 3.082mb    |
| benchExtract1000000Objects () | 3.384s (±0.00%)   | 3.504s (±0.00%)   | 2.984mb         | 2.984mb    |
+-------------------------------+-------------------+-------------------+-----------------+------------+

This comment gets update everytime a new commit comes in!

Covers the exact cache key format and removeWithSubjectId() after store(),
which were not caught by the existing tests. Also drops the rememberKeyId()
call in currentKeyFor(), it only caches the entry by subject, which is
evicted directly anyway.
@DavidBadura
DavidBadura merged commit 5ac8452 into 1.24.x Sep 23, 2026
15 of 16 checks passed
@DavidBadura
DavidBadura deleted the fix-cache-store-decorator-keys branch September 23, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants