-
Mahmoud Aglan authored
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>
905ffefc