From d1b9ffdcf178ef549ed027082938f29f8ff13e41 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Fri, 9 Oct 2026 13:35:54 +0000 Subject: [PATCH] =?UTF-8?q?security:=20Breadcrumbs=20ohne=20Daten=20an=20G?= =?UTF-8?q?litchTip,=20=C3=9Cbergangsmarke=20der=20Session-Sperre=20entfer?= =?UTF-8?q?nt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ErrorReportScrubber entfernt die Daten aller Breadcrumbs und bereinigt ihre Nachrichten; Livewire-Breadcrumbs standardmäßig aus — das SDK hängt dort Formularwerte und Properties an (Roadmap 4.9). Bestand in GlitchTip geprüft: keine Formular- oder Passwortdaten. - SessionRevocation::legacyCachedMark() entfernt (Übergang bis v1.6.0) Changelog: Sicherheit Co-Authored-By: Claude Opus 5.5 --- app/Services/Auth/SessionRevocation.php | 13 +---------- .../Monitoring/ErrorReportScrubber.php | 17 ++++++++++++++ config/sentry.php | 6 +++-- dev/glitch-tip/README.md | 2 +- .../PasswordChangeRevokesSessionsTest.php | 6 ++--- .../Feature/Monitoring/ErrorReportingTest.php | 23 +++++++++++++++++++ 6 files changed, 49 insertions(+), 18 deletions(-) diff --git a/app/Services/Auth/SessionRevocation.php b/app/Services/Auth/SessionRevocation.php index ac18ac98..aae87758 100644 --- a/app/Services/Auth/SessionRevocation.php +++ b/app/Services/Auth/SessionRevocation.php @@ -5,7 +5,6 @@ namespace App\Services\Auth; use App\Models\User; use Illuminate\Auth\Events\Login; use Illuminate\Contracts\Session\Session; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; /** @@ -90,7 +89,7 @@ class SessionRevocation */ public function isRevoked(User $user, Session $session): bool { - $revokedAt = max((int) $user->sessions_revoked_at_ms, $this->legacyCachedMark((int) $user->getKey())); + $revokedAt = (int) $user->sessions_revoked_at_ms; if ($revokedAt === 0) { return false; @@ -98,14 +97,4 @@ class SessionRevocation return (int) $session->get(self::SESSION_KEY, 0) < $revokedAt; } - - /** - * Übergang: Bis v1.5.0 lag die Marke nur im Cache, mit Ablauf nach - * `session.lifetime` + 5 Minuten. Sperren aus dieser Zeit sollen über den - * Deploy hinweg gelten. Kann im Release nach v1.6.0 entfallen. - */ - private function legacyCachedMark(int $userId): int - { - return (int) Cache::get('auth.sessions-revoked-at.'.$userId, 0); - } } diff --git a/app/Support/Monitoring/ErrorReportScrubber.php b/app/Support/Monitoring/ErrorReportScrubber.php index 12cb1c5b..966e8c1d 100644 --- a/app/Support/Monitoring/ErrorReportScrubber.php +++ b/app/Support/Monitoring/ErrorReportScrubber.php @@ -4,6 +4,7 @@ namespace App\Support\Monitoring; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; +use Sentry\Breadcrumb; use Sentry\Event; use Sentry\EventHint; use Sentry\UserDataBag; @@ -41,6 +42,8 @@ class ErrorReportScrubber $event->setUser($userId !== null ? UserDataBag::createFromUserIdentifier($userId) : null); $event->setRequest(self::scrubRequest($event->getRequest())); + $event->setBreadcrumb(array_map(self::scrubBreadcrumb(...), $event->getBreadcrumbs())); + foreach ($event->getExceptions() as $exception) { $exception->setValue(self::scrubText($exception->getValue())); } @@ -58,6 +61,20 @@ class ErrorReportScrubber return $event; } + public static function scrubBreadcrumb(Breadcrumb $breadcrumb): Breadcrumb + { + $message = $breadcrumb->getMessage(); + + return new Breadcrumb( + $breadcrumb->getLevel(), + $breadcrumb->getType(), + $breadcrumb->getCategory(), + $message !== null ? self::scrubText($message) : null, + [], + $breadcrumb->getTimestamp(), + ); + } + /** * @param array $request * @return array diff --git a/config/sentry.php b/config/sentry.php index 2590b2e1..4bd86e00 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -88,8 +88,10 @@ return [ // Capture Laravel cache events (hits, writes etc.) as breadcrumbs 'cache' => env('SENTRY_BREADCRUMBS_CACHE_ENABLED', false), - // Capture Livewire components like routes as breadcrumbs - 'livewire' => env('SENTRY_BREADCRUMBS_LIVEWIRE_ENABLED', true), + // Capture Livewire components like routes as breadcrumbs — aus: hängt + // Formularwerte und Properties an (Roadmap 4.9); der Scrubber leert + // die Daten aller Breadcrumbs zusätzlich. + 'livewire' => env('SENTRY_BREADCRUMBS_LIVEWIRE_ENABLED', false), // Capture SQL queries as breadcrumbs 'sql_queries' => env('SENTRY_BREADCRUMBS_SQL_QUERIES_ENABLED', true), diff --git a/dev/glitch-tip/README.md b/dev/glitch-tip/README.md index 75edfc93..1ede86f4 100644 --- a/dev/glitch-tip/README.md +++ b/dev/glitch-tip/README.md @@ -143,7 +143,7 @@ Wenn der Auftrag lautet „schau in GlitchTip“, „was sagt GlitchTip“ o. Ä ## 5. Regeln, die nicht verletzt werden dürfen -- **Datenschutz (M4):** `send_default_pii` bleibt `false`, `max_request_body_size` bleibt `never`, Log- und Cache-Breadcrumbs bleiben aus, `ErrorReportScrubber` bleibt in `before_send`. Kein Name, keine E-Mail, keine IP an GlitchTip — nur die interne User-ID. Neue Query-Parameter, die mitgehen sollen, nur über `ErrorReportScrubber::AllowedQueryParameters` (und dieselbe Liste in `error-reporting.js` / Umami-Filter). +- **Datenschutz (M4):** `send_default_pii` bleibt `false`, `max_request_body_size` bleibt `never`, Log- und Cache-Breadcrumbs bleiben aus, `ErrorReportScrubber` bleibt in `before_send` und entfernt die Daten aller Breadcrumbs; Livewire-Breadcrumbs bleiben aus (sie tragen Formularwerte und Properties, Roadmap 4.9). Kein Name, keine E-Mail, keine IP an GlitchTip — nur die interne User-ID. Neue Query-Parameter, die mitgehen sollen, nur über `ErrorReportScrubber::AllowedQueryParameters` (und dieselbe Liste in `error-reporting.js` / Umami-Filter). - **Keine variablen Zahlen in Fehlermeldungen**, wenn sie gruppiert werden sollen: GlitchTip gruppiert nach Text. `…after 20002 ms` und `…after 20001 ms` wurden drei getrennte Einträge (Issues 11, 13, 14). Variable Werte in den Kontext, nicht in die Meldung — oder einen Fingerprint setzen wie `ErrorSignal`. - **Vorübergehende Fremdfehler nicht einzeln melden.** Sonst flutet ein Google-Ausfall GlitchTip und verdeckt echte Fehler. - **Tests melden nie an GlitchTip** (Abschnitt 2). Neue Sentry-Tests nur mit `FakeGlitchTip`. diff --git a/tests/Feature/Auth/PasswordChangeRevokesSessionsTest.php b/tests/Feature/Auth/PasswordChangeRevokesSessionsTest.php index 7400b97b..f6414d3b 100644 --- a/tests/Feature/Auth/PasswordChangeRevokesSessionsTest.php +++ b/tests/Feature/Auth/PasswordChangeRevokesSessionsTest.php @@ -190,7 +190,7 @@ test('the revocation mark is stored on the account and survives a cache flush', $this->assertGuest(); }); -test('a revocation from before v1.6.0 that only lives in the cache still applies', function () { +test('a cache-only mark from before v1.6.0 no longer revokes sessions', function () { /** @var TestCase $this */ [$user, $otherSessionStamp] = accountWithOtherSession(); @@ -199,6 +199,6 @@ test('a revocation from before v1.6.0 that only lives in the cache still applies $this->actingAs($user->fresh()) ->withSession([SessionRevocation::SESSION_KEY => $otherSessionStamp]) ->get(route('me.security')) - ->assertRedirect(route('login')); - $this->assertGuest(); + ->assertSuccessful(); + $this->assertAuthenticatedAs($user); }); diff --git a/tests/Feature/Monitoring/ErrorReportingTest.php b/tests/Feature/Monitoring/ErrorReportingTest.php index d02e22ab..3a17c185 100644 --- a/tests/Feature/Monitoring/ErrorReportingTest.php +++ b/tests/Feature/Monitoring/ErrorReportingTest.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Support\Facades\Route; use Livewire\Features\SupportFileUploads\MissingFileUploadsTraitException; use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException; +use Sentry\Breadcrumb; use Sentry\Event; use Sentry\ExceptionDataBag; use Sentry\SentrySdk; @@ -110,6 +111,28 @@ test('messages and exception values lose e-mail addresses', function () { ->and($scrubbed?->getUser())->toBeNull(); }); +test('breadcrumbs lose their data, so form values never reach glitchtip', function () { + $event = Event::createEvent(); + $event->setBreadcrumb([ + new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, 'livewire', 'Component booted: customer.security', [ + 'updates' => ['password' => 'Geheim-12345!', 'email' => 'kunde@example.com'], + ]), + new Breadcrumb(Breadcrumb::LEVEL_INFO, Breadcrumb::TYPE_DEFAULT, 'db.sql.query', 'select * from users where email = info@firma.de', ['executionTimeMs' => 1.2]), + ]); + + $breadcrumbs = ErrorReportScrubber::beforeSend($event)?->getBreadcrumbs() ?? []; + + expect($breadcrumbs)->toHaveCount(2) + ->and($breadcrumbs[0]->getMetadata())->toBe([]) + ->and($breadcrumbs[0]->getMessage())->toBe('Component booted: customer.security') + ->and($breadcrumbs[1]->getMetadata())->toBe([]) + ->and($breadcrumbs[1]->getMessage())->toBe('select * from users where email = [email]'); +}); + +test('livewire breadcrumbs are switched off by default', function () { + expect(config('sentry.breadcrumbs.livewire'))->toBeFalse(); +}); + test('the test suite never reports to the real glitchtip', function () { // Ohne diese Absicherung landeten absichtlich provozierte Fehler // („Queue weg“, „Datenbank nicht erreichbar“) als Issues in GlitchTip.