*/ public const AllowedQueryParameters = ['page', 'q', 'tab', 'sort']; private const EmailPattern = '/[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}/i'; /** Pfadsegmente, die wie ein Token aussehen (Magic Links, Reset-Links). */ private const TokenSegmentPattern = '#(?<=/)[A-Za-z0-9_\-]{32,}(?=/|$)#'; public static function beforeSend(Event $event, ?EventHint $hint = null): ?Event { $userId = self::authenticatedUserId(); $event->setUser($userId !== null ? UserDataBag::createFromUserIdentifier($userId) : null); $event->setRequest(self::scrubRequest($event->getRequest())); foreach ($event->getExceptions() as $exception) { $exception->setValue(self::scrubText($exception->getValue())); } if ($event->getMessage() !== null) { $formatted = $event->getMessageFormatted(); $event->setMessage( self::scrubText($event->getMessage()), $event->getMessageParams(), $formatted !== null ? self::scrubText($formatted) : null, ); } return $event; } /** * @param array $request * @return array */ public static function scrubRequest(array $request): array { if (isset($request['url']) && is_string($request['url'])) { $request['url'] = self::scrubUrl($request['url']); } if (isset($request['query_string']) && is_string($request['query_string'])) { $request['query_string'] = self::scrubQuery($request['query_string']); } unset($request['data'], $request['cookies']); foreach ($request['headers'] ?? [] as $name => $values) { if (strtolower((string) $name) === 'referer') { $request['headers'][$name] = array_map( static fn (mixed $value): string => self::scrubUrl((string) $value), (array) $values, ); } } return $request; } public static function scrubUrl(string $url): string { $query = parse_url($url, PHP_URL_QUERY); $withoutQuery = $query !== null && $query !== false ? Str::before($url, '?') : $url; $scrubbedPath = self::scrubPath(self::scrubPathTokens($withoutQuery)); return $query !== null && $query !== false && $query !== '' ? $scrubbedPath.'?'.self::scrubQuery($query) : $scrubbedPath; } public static function scrubQuery(string $query): string { parse_str($query, $parameters); $scrubbed = []; foreach ($parameters as $name => $value) { $isAllowed = in_array((string) $name, self::AllowedQueryParameters, true) || str_starts_with((string) $name, 'utm_'); $scrubbed[$name] = $isAllowed && is_string($value) ? $value : self::Filtered; } return http_build_query($scrubbed, '', '&', PHP_QUERY_RFC3986); } public static function scrubPath(string $path): string { return self::scrubText((string) preg_replace(self::TokenSegmentPattern, self::Filtered, $path)); } public static function scrubText(string $text): string { return (string) preg_replace(self::EmailPattern, '[email]', $text); } /** * Ersetzt Routenparameter, deren Name auf ein Geheimnis hindeutet * (`token`, `hash`, `signature`), im Pfad der aktuellen Route. */ private static function scrubPathTokens(string $url): string { try { $route = request()->route(); } catch (Throwable) { return $url; } if ($route === null) { return $url; } foreach ($route->parameters() as $name => $value) { if (is_string($value) && $value !== '' && Str::contains(strtolower((string) $name), ['token', 'hash', 'signature'])) { $url = str_replace(rawurlencode($value), self::Filtered, str_replace($value, self::Filtered, $url)); } } return $url; } private static function authenticatedUserId(): int|string|null { try { return Auth::id(); } catch (Throwable) { return null; } } }