security: Breadcrumbs ohne Daten an GlitchTip, Übergangsmarke der Session-Sperre entfernt
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
29e68330ca
commit
d1b9ffdcf1
6 changed files with 49 additions and 18 deletions
|
|
@ -5,7 +5,6 @@ namespace App\Services\Auth;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Auth\Events\Login;
|
use Illuminate\Auth\Events\Login;
|
||||||
use Illuminate\Contracts\Session\Session;
|
use Illuminate\Contracts\Session\Session;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,7 +89,7 @@ class SessionRevocation
|
||||||
*/
|
*/
|
||||||
public function isRevoked(User $user, Session $session): bool
|
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) {
|
if ($revokedAt === 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -98,14 +97,4 @@ class SessionRevocation
|
||||||
|
|
||||||
return (int) $session->get(self::SESSION_KEY, 0) < $revokedAt;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ namespace App\Support\Monitoring;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Sentry\Breadcrumb;
|
||||||
use Sentry\Event;
|
use Sentry\Event;
|
||||||
use Sentry\EventHint;
|
use Sentry\EventHint;
|
||||||
use Sentry\UserDataBag;
|
use Sentry\UserDataBag;
|
||||||
|
|
@ -41,6 +42,8 @@ class ErrorReportScrubber
|
||||||
$event->setUser($userId !== null ? UserDataBag::createFromUserIdentifier($userId) : null);
|
$event->setUser($userId !== null ? UserDataBag::createFromUserIdentifier($userId) : null);
|
||||||
$event->setRequest(self::scrubRequest($event->getRequest()));
|
$event->setRequest(self::scrubRequest($event->getRequest()));
|
||||||
|
|
||||||
|
$event->setBreadcrumb(array_map(self::scrubBreadcrumb(...), $event->getBreadcrumbs()));
|
||||||
|
|
||||||
foreach ($event->getExceptions() as $exception) {
|
foreach ($event->getExceptions() as $exception) {
|
||||||
$exception->setValue(self::scrubText($exception->getValue()));
|
$exception->setValue(self::scrubText($exception->getValue()));
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +61,20 @@ class ErrorReportScrubber
|
||||||
return $event;
|
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<string, mixed> $request
|
* @param array<string, mixed> $request
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,10 @@ return [
|
||||||
// Capture Laravel cache events (hits, writes etc.) as breadcrumbs
|
// Capture Laravel cache events (hits, writes etc.) as breadcrumbs
|
||||||
'cache' => env('SENTRY_BREADCRUMBS_CACHE_ENABLED', false),
|
'cache' => env('SENTRY_BREADCRUMBS_CACHE_ENABLED', false),
|
||||||
|
|
||||||
// Capture Livewire components like routes as breadcrumbs
|
// Capture Livewire components like routes as breadcrumbs — aus: hängt
|
||||||
'livewire' => env('SENTRY_BREADCRUMBS_LIVEWIRE_ENABLED', true),
|
// 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
|
// Capture SQL queries as breadcrumbs
|
||||||
'sql_queries' => env('SENTRY_BREADCRUMBS_SQL_QUERIES_ENABLED', true),
|
'sql_queries' => env('SENTRY_BREADCRUMBS_SQL_QUERIES_ENABLED', true),
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ Wenn der Auftrag lautet „schau in GlitchTip“, „was sagt GlitchTip“ o. Ä
|
||||||
|
|
||||||
## 5. Regeln, die nicht verletzt werden dürfen
|
## 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`.
|
- **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.
|
- **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`.
|
- **Tests melden nie an GlitchTip** (Abschnitt 2). Neue Sentry-Tests nur mit `FakeGlitchTip`.
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ test('the revocation mark is stored on the account and survives a cache flush',
|
||||||
$this->assertGuest();
|
$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 */
|
/** @var TestCase $this */
|
||||||
[$user, $otherSessionStamp] = accountWithOtherSession();
|
[$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())
|
$this->actingAs($user->fresh())
|
||||||
->withSession([SessionRevocation::SESSION_KEY => $otherSessionStamp])
|
->withSession([SessionRevocation::SESSION_KEY => $otherSessionStamp])
|
||||||
->get(route('me.security'))
|
->get(route('me.security'))
|
||||||
->assertRedirect(route('login'));
|
->assertSuccessful();
|
||||||
$this->assertGuest();
|
$this->assertAuthenticatedAs($user);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Livewire\Features\SupportFileUploads\MissingFileUploadsTraitException;
|
use Livewire\Features\SupportFileUploads\MissingFileUploadsTraitException;
|
||||||
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
|
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
|
||||||
|
use Sentry\Breadcrumb;
|
||||||
use Sentry\Event;
|
use Sentry\Event;
|
||||||
use Sentry\ExceptionDataBag;
|
use Sentry\ExceptionDataBag;
|
||||||
use Sentry\SentrySdk;
|
use Sentry\SentrySdk;
|
||||||
|
|
@ -110,6 +111,28 @@ test('messages and exception values lose e-mail addresses', function () {
|
||||||
->and($scrubbed?->getUser())->toBeNull();
|
->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 () {
|
test('the test suite never reports to the real glitchtip', function () {
|
||||||
// Ohne diese Absicherung landeten absichtlich provozierte Fehler
|
// Ohne diese Absicherung landeten absichtlich provozierte Fehler
|
||||||
// („Queue weg“, „Datenbank nicht erreichbar“) als Issues in GlitchTip.
|
// („Queue weg“, „Datenbank nicht erreichbar“) als Issues in GlitchTip.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue