fix(cache): stop refusing to restore the objects we cache on purpose
Every branded page on the live tenant answered 500 with
"BrandingService::for(): Return value must be of type BrandProfile,
__PHP_Incomplete_Class returned".
config/cache.php shipped Laravel's default serializable_classes => false,
which unserializes cache values with allowed_classes: false. That is safe for
an app that caches only scalars and arrays, and fatal for one that does not.
We cache whole value objects deliberately: BrandProfile is the entire tenant
brand, resolved once and held until branding changes, and read by every admin,
portal and print layout. With classes refused it came back as
__PHP_Incomplete_Class, the return type threw, and the admin went dark.
The setting exists to stop a gadget chain in a cache an attacker can already
write to. Ours is the tenant's own Postgres, reachable only by the app;
anyone who can write there can do worse directly. CACHE_SERIALIZABLE_CLASSES
lets a deployment pass its own allowlist without a code change.
BrandingService now also checks what the cache handed back before trusting it,
and rebuilds when it is not a profile. A cache that cannot return this class
should cost a rebuild per request, never a 500 — the failure has to degrade,
not detonate.
Verified: every admin screen and the group roster render against a restored
copy of the live tenant with CACHE_STORE=database.
Co-Authored-By:
Claude Opus 5 (1M context) <noreply@anthropic.com>
Showing
Please register or sign in to comment