Overview
CVE-2026-41903 describes a broken object-level authorization (OLA) flaw in FreeScout built on Laravel where a user possessing PERM_EDIT_USERS could read and modify another user’s notification subscriptions by sending a crafted POST request. This allowed a non-admin to silently disable critical notifications for admins, including security alerts and conversation-assignment notices, enabling potential operational blind spots and security risk. The issue is related to CVE-2025-48472 in that the prior fix did not cover this specific code path, leaving a gap in access control around user-notification data. FreeScout patched this by releasing version 1.8.217, but the vulnerability class remains a common risk when object-level permissions are not consistently enforced across all code paths that touch per-user resources in Laravel apps. In Laravel terms, this vulnerability manifests when controllers perform updates on a User model’s notification settings without validating that the authenticated user is allowed to modify that specific user’s data. It underscores the importance of binding models and enforcing policies for any action that could affect another user’s data, especially for admin-like capabilities and sensitive notification preferences. The remediation is to implement explicit authorization checks (policies or gates) for object-level actions and to ensure route-model binding and input validation are used to prevent privilege escalation via crafted requests.
Affected Versions
FreeScout prior to 1.8.217 (versions up to 1.8.216)
Code Fix Example
Laravel API Security Remediation
Vulnerable pattern (no proper authorization):
<?php
use App\Models\User;
use Illuminate\Http\Request;
class NotificationController extends Controller
{
// Vulnerable: relies on request data and lacks per-object authorization
public function updateVulnerable(Request $request)
{
// Attacker can supply any user_id and modify that user's subscriptions
$targetUser = User::findOrFail($request->input('user_id'));
$targetUser->notification_subscriptions = $request->input('notifications');
$targetUser->save();
return response()->json(['status' => 'updated']);
}
}
// Fixed pattern (authorization enforced):
use App\Models\User;
use Illuminate\Http\Request;
class NotificationController extends Controller
{
// Route model binding + policy check
public function updateFixed(Request $request, User $user)
{
// Enforce object-level authorization: only the owner or an authorized admin can update
$this->authorize('updateNotifications', $user);
$validated = $request->validate([
'notifications' => 'array',
'notifications.*' => 'string',
]);
$user->notification_subscriptions = $validated['notifications'];
$user->save();
return response()->json(['status' => 'updated']);
}
}
// Policy (define who can update another user's notifications)
class UserPolicy
{
// Allow if the authenticated user is the target user or has explicit permission to manage notifications
public function updateNotifications(User $authUser, User $targetUser)
{
return $authUser->id === $targetUser->id || $authUser->can('manage_notifications');
}
}
// Gate binding (optional alternative to policy if you’re not using policies):
Gate::define('updateNotifications', function ($authUser, $targetUser) {
return $authUser->id === $targetUser->id || $authUser->can('manage_notifications');
});