diff --git a/app/init/realtime/connection.php b/app/init/realtime/connection.php index c02da3058e..3bb91a3aeb 100644 --- a/app/init/realtime/connection.php +++ b/app/init/realtime/connection.php @@ -329,7 +329,10 @@ return function (Container $container): void { // impersonateUserId also accepts a query param to support embedding in WebSocket URLs. // Email and phone are intentionally header-only to avoid PII exposure in proxy/LB logs. - $impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', (string)$request->getParam('impersonateUserId', '')); + // Query-param fallback is blocked for cross-site requests to prevent CSRF attacks via + // third-party pages; Sec-Fetch-Site is a browser-enforced forbidden header. + $isCrossSite = $request->getHeader('sec-fetch-site', '') === 'cross-site'; + $impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', $isCrossSite ? '' : (string)$request->getParam('impersonateUserId', '')); $impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', ''); $impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', ''); diff --git a/app/init/resources/request.php b/app/init/resources/request.php index d1c0d2bea0..143adca352 100644 --- a/app/init/resources/request.php +++ b/app/init/resources/request.php @@ -574,7 +574,10 @@ return function (Container $container): void { // Impersonation: if current user has impersonator capability and headers/params are set, act as another user // impersonateUserId also accepts a query param to allow embedding in direct file/image URLs (e.g. ) // where custom headers cannot be set. Email and phone are intentionally header-only to avoid PII in URLs/logs. - $impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', (string)$request->getParam('impersonateUserId', '')); + // Query-param fallback is blocked for cross-site requests (Sec-Fetch-Site: cross-site) to prevent CSRF; + // Sec-Fetch-Site is a browser-enforced forbidden header that cannot be spoofed by JavaScript. + $isCrossSite = $request->getHeader('sec-fetch-site', '') === 'cross-site'; + $impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', $isCrossSite ? '' : (string)$request->getParam('impersonateUserId', '')); $impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', ''); $impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', ''); if (!$user->isEmpty() && $user->getAttribute('impersonator', false)) {