From 403124948927fa96dbb898a7e65ef24da51d87a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=CC=81ctor=20Falco=CC=81n?= Date: Sat, 4 Jul 2026 20:13:38 +0200 Subject: [PATCH] Hide sensitive User fields from serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The User model's $hidden array only covered password and 2FA/remember-token fields, leaving Cashier billing columns (stripe_id, pm_type, pm_last_four, trial_ends_at) and the legacy encryption_salt exposed in every serialized User — including the Inertia-shared auth.user prop sent to the browser. None of these fields are read by the frontend, and Cashier continues to read them server-side unaffected, so hiding them removes the leak without any UI or billing impact. Adds an InertiaSharedDataTest case asserting the shared auth.user prop omits all sensitive fields. --- app/Models/User.php | 5 +++++ tests/Feature/InertiaSharedDataTest.php | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/app/Models/User.php b/app/Models/User.php index 973e1c16..e128f2af 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -62,6 +62,11 @@ class User extends Authenticatable implements HasLocalePreference, MustVerifyEma 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token', + 'stripe_id', + 'pm_type', + 'pm_last_four', + 'trial_ends_at', + 'encryption_salt', ]; /** diff --git a/tests/Feature/InertiaSharedDataTest.php b/tests/Feature/InertiaSharedDataTest.php index a2d2c691..cb9d8a75 100644 --- a/tests/Feature/InertiaSharedDataTest.php +++ b/tests/Feature/InertiaSharedDataTest.php @@ -27,6 +27,31 @@ test('authenticated users receive auth user in shared props', function () { ); }); +test('shared auth user does not expose sensitive fields', function () { + $user = User::factory()->onboarded()->create([ + 'stripe_id' => 'cus_test123', + 'pm_type' => 'card', + 'pm_last_four' => '4242', + 'trial_ends_at' => now()->addDays(7), + 'encryption_salt' => str_repeat('a', 24), + ]); + + $response = actingAs($user)->withoutVite()->get(route('dashboard')); + + $response->assertInertia(fn (Assert $page) => $page + ->where('auth.user.email', $user->email) + ->missing('auth.user.stripe_id') + ->missing('auth.user.pm_type') + ->missing('auth.user.pm_last_four') + ->missing('auth.user.trial_ends_at') + ->missing('auth.user.encryption_salt') + ->missing('auth.user.password') + ->missing('auth.user.two_factor_secret') + ->missing('auth.user.two_factor_recovery_codes') + ->missing('auth.user.remember_token') + ); +}); + test('all pages receive app url in shared props', function () { $response = $this->withoutVite()->get(route('home'));