Compare commits
4 commits
35a49ccc2b
...
28e78f5d57
| Author | SHA1 | Date | |
|---|---|---|---|
| 28e78f5d57 | |||
| 69aaef4cb5 | |||
| 87ad433bca | |||
| 6a86fa488e |
12 changed files with 109 additions and 6 deletions
|
|
@ -238,6 +238,19 @@ class AdminSystemHealth
|
|||
: 'SESSION_SECURE_COOKIE ist aus'.($isLocal ? ' (lokal ok).' : ' — in https-Umgebungen aktivieren.'),
|
||||
];
|
||||
|
||||
// Imagick trägt Herkunftsprüfung und KI-Vorab-Check der Bilder. Fehlt
|
||||
// die Erweiterung, scheitern beide still (Bilder werden durchgelassen)
|
||||
// — so geschehen auf Live bis 08.10.2026.
|
||||
$precheckEnabled = (bool) config('images.precheck.enabled');
|
||||
$checks[] = [
|
||||
'key' => 'imagick',
|
||||
'label' => 'PHP-Erweiterung Imagick',
|
||||
'status' => $this->imagickAvailable() ? self::StatusOk : ($precheckEnabled ? self::StatusCritical : self::StatusWarn),
|
||||
'detail' => $this->imagickAvailable()
|
||||
? 'Imagick ist geladen (Herkunftsprüfung und KI-Vorab-Check der Bilder).'
|
||||
: 'Imagick fehlt — Herkunftsprüfung'.($precheckEnabled ? ' und KI-Vorab-Check' : '').' der Bilder laufen ins Leere. Auf dem Server php8.4-imagick installieren und PHP-FPM neu laden.',
|
||||
];
|
||||
|
||||
// Herkunftsprüfung der Bild-Uploads (Bildkennzeichnung E.7): Fehler
|
||||
// der letzten sieben Tage zählen; Zustand D im Formular blockiert
|
||||
// nichts, muss aber sichtbar bleiben.
|
||||
|
|
@ -341,6 +354,11 @@ class AdminSystemHealth
|
|||
return $checks;
|
||||
}
|
||||
|
||||
protected function imagickAvailable(): bool
|
||||
{
|
||||
return class_exists(\Imagick::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $queue
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -21,8 +21,11 @@ use Illuminate\Http\Request;
|
|||
use Illuminate\Support\Facades\Route;
|
||||
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
|
||||
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;
|
||||
use Livewire\Features\SupportFileUploads\MissingFileUploadsTraitException;
|
||||
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
|
||||
use Sentry\Laravel\Integration;
|
||||
use Symfony\Component\Console\Exception\CommandNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
|
|
@ -177,4 +180,18 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
// Vertippte artisan-Befehle sind kein Anwendungsfehler (GlitchTip
|
||||
// server-pressekonto, Issue 9: „config:cachels“ auf Live).
|
||||
$exceptions->dontReport(CommandNotFoundException::class);
|
||||
|
||||
// Manipulierte Livewire-Anfragen (Scanner, die gesperrte Properties
|
||||
// setzen oder Uploads an Komponenten ohne Upload schicken) als 400
|
||||
// beantworten statt als 500 bzw. 419 — Livewire weist sie korrekt ab,
|
||||
// sie sind kein Anwendungsfehler und kein abgelaufenes Formular
|
||||
// (GlitchTip server-pressekonto, Issues 38 und 39, 08.10.2026).
|
||||
$exceptions->map(
|
||||
CannotUpdateLockedPropertyException::class,
|
||||
fn (CannotUpdateLockedPropertyException $e): BadRequestHttpException => new BadRequestHttpException('Ungültige Livewire-Anfrage.', $e),
|
||||
);
|
||||
$exceptions->map(
|
||||
MissingFileUploadsTraitException::class,
|
||||
fn (MissingFileUploadsTraitException $e): BadRequestHttpException => new BadRequestHttpException('Ungültige Livewire-Anfrage.', $e),
|
||||
);
|
||||
})->create();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
|
||||
## 1. Server-Voraussetzungen
|
||||
|
||||
- PHP 8.4 (FPM) mit Extensions: `pdo_mysql`, `mbstring`, `intl`, `gd` (Bildvarianten), `curl`, `zip`
|
||||
- PHP 8.4 (FPM) mit Extensions: `pdo_mysql`, `mbstring`, `intl`, `gd` (Bildvarianten), `curl`, `zip`, `exif`, `imagick`
|
||||
- `imagick` trägt die Herkunftsprüfung (IPTC/XMP) und den KI-Vorab-Check der Bilder. Fehlt es, laufen beide ins Leere, ohne etwas zu blockieren. Live fehlte es bis 08.10.2026. Einrichtung: `sudo apt install php8.4-imagick`, dann `sudo service php8.4-fpm reload` und `php artisan queue:restart`. Prüfen: `php -m | grep -i imagick` und Admin ▸ Reports ▸ Betriebsüberwachung, Zeile „PHP-Erweiterung Imagick“.
|
||||
- MySQL/MariaDB (Prod: `DB_CONNECTION=mysql`; SQLite ist nur Dev)
|
||||
- Node.js (LTS) + npm — nur zum Bauen der Assets (Builds sind **gitignored**, siehe Schritt 3)
|
||||
- Webserver-vHosts für **alle drei Domains** auf dasselbe `public/` (Host-Routing macht die App)
|
||||
|
|
|
|||
|
|
@ -263,3 +263,4 @@ Ausfall des Anbieters keinen Deploy abbricht.
|
|||
| 07.10.2026 | `v1.4.0` | Sicherheitsaudit S-01–S-06, Passkeys, Zweitfaktor-Pflicht für Admins; Migration `create_passkeys_table` (`--migrations`). Vorher brach `deploy.sh` wegen Abweichungen ab: Die Doku-Commits nach `v1.3.2` waren von Hand hochgeladen worden, `deploy.sh` hatte dabei sein Ausführungsrecht verloren. Mit `git checkout -- dev/betrieb/…` bereinigt. **Danach entzog `memberships:resync-auto` 19.598 von 19.607 automatischen Firmenrechten**: Bestandskonten ohne `email_verified_at` verloren pauschal alles. |
|
||||
| 07.10.2026 | `v1.4.1` | Hotfix für den Abgleich: Entzogen wird nur noch, was keine Grundlage mehr hat; das Kommando läuft in einer Transaktion, mit `--dry-run` und Notbremse ab 5 %. Rechte aus dem Backup vor dem Deploy wiederhergestellt (`INSERT IGNORE`, 19.598 eingefügt, danach 19.607). Hinweis: `mysql` auf dem Server braucht `-h`/`-P` aus der `.env`, sonst „Access denied“ für `…@'localhost'`; ohne Passwort geht es über `php artisan tinker`. Probelauf 19.607 → 19.316, danach ausgeführt. Wiederholung am 08.10. ohne Änderung (19.316 Rechte, 14.911 User). Die Nacht verlief ohne Auffälligkeiten. |
|
||||
| 08.10.2026 | `v1.5.0` | API-Zugang an die Buchung gebunden (403 ohne Abo/Buchung, Tokens bleiben), optionales Token-Ablaufdatum, `sanctum:prune-expired` täglich 04:10. Vorab auf Live geprüft: nginx antwortet direkt, kein Proxy/CDN; Cookies `secure`/`httponly`/`samesite=lax`, HSTS fehlte. Nach dem Deploy: `strict-transport-security: max-age=31536000`, `trustedproxy.proxies` = `null`. Auswirkung der Buchungsprüfung: 2 Konten haben die API in den letzten 30 Tagen genutzt, beide weiterhin mit Zugang. Keine Migration. |
|
||||
| 08.10.2026 | `v1.6.0` | Mit Migration (`users.sessions_revoked_at_ms`, Batch 22). Härtung aus Phase 4 des Sicherheitsaudits: „Andere Geräte abmelden“, IP-weites Login-Limit (20 Fehlversuche in 15 Minuten), dauerhafte Session-Sperrmarke, Passwörter ab 12 Zeichen mit Leak-Abgleich, CSP im Melde-Modus (`storage/logs/csp-*.log`), npm `shell-quote`/`source-map-js`. Nach dem Deploy: Header `content-security-policy-report-only` vorhanden, Umami- und GlitchTip-Host in `script-src`/`connect-src`. CSP-Log in einigen Tagen auswerten. |
|
||||
|
|
|
|||
|
|
@ -2,7 +2,17 @@
|
|||
|
||||
**Grundlage:** [SICHERHEITSAUDIT-2026-10-07.md](SICHERHEITSAUDIT-2026-10-07.md), Repository-Stand `2215826`
|
||||
|
||||
**Stand:** 08.10.2026 – Phasen 1–3 und Entscheidung 5 (Passkeys, Pflicht-Zweitfaktor für Admins) live seit `v1.4.0` (07.10.2026), Hotfix `v1.4.1`; Abgleich der Firmenrechte ausgeführt (19.607 → 19.316, Wiederholung stabil). API-Zugang an Buchung, Proxy und HSTS live seit `v1.5.0` (08.10.2026); restliche Phase 4 umgesetzt für `v1.6.0` (Abschnitt 6)
|
||||
**Stand:** 08.10.2026 – Phasen 1–3 und Entscheidung 5 (Passkeys, Pflicht-Zweitfaktor für Admins) live seit `v1.4.0` (07.10.2026), Hotfix `v1.4.1`; Abgleich der Firmenrechte ausgeführt (19.607 → 19.316, Wiederholung stabil). API-Zugang an Buchung, Proxy und HSTS live seit `v1.5.0` (08.10.2026); restliche Phase 4 live seit `v1.6.0` (08.10.2026, Abschnitt 6)
|
||||
|
||||
**Wiedervorlage — nicht vergessen:**
|
||||
|
||||
| Wann | Was | Wie |
|
||||
| --- | --- | --- |
|
||||
| ~~sofort~~ | ✅ 08.10.2026: Imagick auf Live installiert (`php -m` zeigt `imagick`). Vorher liefen Herkunftsprüfung und KI-Vorab-Check der Bilder ins Leere. | Nach `v1.6.1` in der Betriebsüberwachung, Zeile „PHP-Erweiterung Imagick“, gegenprüfen (zeigt den Stand von PHP-FPM). |
|
||||
| **ab 15.10.2026** | CSP-Meldungen auswerten (Melde-Modus seit `v1.6.0`). | Auf Live `tail -n 200 storage/logs/csp-*.log`; fehlende Quellen in `App\Support\ContentSecurityPolicy` ergänzen, danach über das Scharfschalten entscheiden (`'unsafe-inline'`/`'unsafe-eval'` bleiben bis zu einer Nonce-Lösung). |
|
||||
| nächstes Release | Übergangs-Fallback der Sperrmarke entfernen. | `SessionRevocation::legacyCachedMark()` und den Test „a revocation from before v1.6.0 …“ streichen. |
|
||||
| nächstes Release | Liste und Kennzahl „Aktive Sessions“ auf der Sicherheitsseite lesen die Tabelle `sessions`, Live speichert Sessions in Redis — dort immer leer. | Korrigieren oder entfernen; der Knopf „Andere Geräte abmelden“ ist davon unabhängig. |
|
||||
| bei Gelegenheit | Außerhalb des Audits: eigene MySQL-Benutzer je Projekt auf `global-mysql`, MySQL-Root-Passwort ändern (steht in der Git-Historie), lokalen `APP_KEY` reparieren. | – |
|
||||
|
||||
**Ausführung:** Befehle direkt im Dev-Container (`php artisan …`, ohne Sail – siehe `.ai/rules/general.md`)
|
||||
|
||||
|
|
@ -114,6 +124,7 @@ Alles zusammen geht als ein Release raus. Vorgeschlagen ist `v1.4.0`: neue Funkt
|
|||
**Offen geblieben:**
|
||||
|
||||
- **Phase 4 (Härtung):** inzwischen erledigt, Stand in Abschnitt 6.
|
||||
- Wiedervorlage der Restpunkte: Tabelle oben unter „Stand“.
|
||||
|
||||
## 1. Nachprüfung des Audits
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
const y="modulepreload",A=function(e){return"https://pressekonto.test/build/"+e},h={},_=function(r,t,a){let u=Promise.resolve();if(t&&t.length>0){let o=function(s){return Promise.all(s.map(c=>Promise.resolve(c).then(f=>({status:"fulfilled",value:f}),f=>({status:"rejected",reason:f}))))};document.getElementsByTagName("link");const n=document.querySelector("meta[property=csp-nonce]"),m=(n==null?void 0:n.nonce)||(n==null?void 0:n.getAttribute("nonce"));u=o(t.map(s=>{if(s=A(s),s in h)return;h[s]=!0;const c=s.endsWith(".css"),f=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${f}`))return;const l=document.createElement("link");if(l.rel=c?"stylesheet":y,c||(l.as="script"),l.crossOrigin="",l.href=s,m&&l.setAttribute("nonce",m),document.head.appendChild(l),c)return new Promise((b,S)=>{l.addEventListener("load",b),l.addEventListener("error",()=>S(new Error(`Unable to preload CSS for ${s}`)))})}))}function d(o){const n=new Event("vite:preloadError",{cancelable:!0});if(n.payload=o,window.dispatchEvent(n),!n.defaultPrevented)throw o}return u.then(o=>{for(const n of o||[])n.status==="rejected"&&d(n.reason);return r().catch(d)})},i=(()=>{const e=document.querySelector('meta[name="pk-error-reporting"]');try{return e?JSON.parse(e.content):null}catch{return null}})(),k=["src","pr","ref","page","q","tab","edition"],v=new RegExp("(?<=\\/)[A-Za-z0-9_-]{32,}(?=\\/|$)","g"),w=/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;function p(e){try{const r=new URL(e,window.location.origin);r.pathname=r.pathname.replace(v,"[Filtered]");for(const t of[...r.searchParams.keys()])!k.includes(t)&&!t.startsWith("utm_")&&r.searchParams.set(t,"[Filtered]");return r.hash="",r.toString().replace(w,"[email]")}catch{return"[Filtered]"}}let E=null;function P(){return E??(E=_(()=>import("./error-reporting-sdk-Ca8cRdCE.js"),[]).then(e=>(e.init({dsn:i.dsn,release:i.release||void 0,environment:i.environment,sampleRate:i.sampleRate??1,sendDefaultPii:!1,integrations:r=>r.filter(t=>!["GlobalHandlers","Breadcrumbs","BrowserSession","BrowserApiErrors"].includes(t.name)),ignoreErrors:["ResizeObserver loop","Script error.","AbortError"],denyUrls:[/^chrome-extension:\/\//,/^moz-extension:\/\//,/^safari-(web-)?extension:\/\//],beforeSend(r){var t,a,u,d;(t=r.request)!=null&&t.url&&(r.request.url=p(r.request.url)),(u=(a=r.request)==null?void 0:a.headers)!=null&&u.Referer&&(r.request.headers.Referer=p(r.request.headers.Referer));for(const o of((d=r.exception)==null?void 0:d.values)??[])o.value&&(o.value=o.value.replace(w,"[email]"));return r}}),i.userId&&e.setUser({id:String(i.userId)}),e.setTag("area",i.area),e))),E}function R(e){var a;if(e instanceof Error)return e;const r=((a=e==null?void 0:e.constructor)==null?void 0:a.name)??typeof e;let t;try{t=typeof e=="string"?e:JSON.stringify(e)}catch{t=String(e)}return new Error(`Unbehandelte Ablehnung (${r}): ${String(t??"").slice(0,300)}`)}function g(e){P().then(r=>r.captureException(e)).catch(()=>{})}i!=null&&i.dsn&&(window.addEventListener("error",e=>{e.error&&g(e.error)}),window.addEventListener("unhandledrejection",e=>{g(R(e.reason))}));
|
||||
const y="modulepreload",A=function(e){return"https://pressekonto.test/build/"+e},h={},_=function(r,t,a){let u=Promise.resolve();if(t&&t.length>0){let o=function(s){return Promise.all(s.map(c=>Promise.resolve(c).then(f=>({status:"fulfilled",value:f}),f=>({status:"rejected",reason:f}))))};document.getElementsByTagName("link");const n=document.querySelector("meta[property=csp-nonce]"),m=(n==null?void 0:n.nonce)||(n==null?void 0:n.getAttribute("nonce"));u=o(t.map(s=>{if(s=A(s),s in h)return;h[s]=!0;const c=s.endsWith(".css"),f=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${f}`))return;const l=document.createElement("link");if(l.rel=c?"stylesheet":y,c||(l.as="script"),l.crossOrigin="",l.href=s,m&&l.setAttribute("nonce",m),document.head.appendChild(l),c)return new Promise((b,S)=>{l.addEventListener("load",b),l.addEventListener("error",()=>S(new Error(`Unable to preload CSS for ${s}`)))})}))}function d(o){const n=new Event("vite:preloadError",{cancelable:!0});if(n.payload=o,window.dispatchEvent(n),!n.defaultPrevented)throw o}return u.then(o=>{for(const n of o||[])n.status==="rejected"&&d(n.reason);return r().catch(d)})},i=(()=>{const e=document.querySelector('meta[name="pk-error-reporting"]');try{return e?JSON.parse(e.content):null}catch{return null}})(),k=["src","pr","ref","page","q","tab","edition"],P=new RegExp("(?<=\\/)[A-Za-z0-9_-]{32,}(?=\\/|$)","g"),w=/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;function p(e){try{const r=new URL(e,window.location.origin);r.pathname=r.pathname.replace(P,"[Filtered]");for(const t of[...r.searchParams.keys()])!k.includes(t)&&!t.startsWith("utm_")&&r.searchParams.set(t,"[Filtered]");return r.hash="",r.toString().replace(w,"[email]")}catch{return"[Filtered]"}}let E=null;function R(){return E??(E=_(()=>import("./error-reporting-sdk-Ca8cRdCE.js"),[]).then(e=>(e.init({dsn:i.dsn,release:i.release||void 0,environment:i.environment,sampleRate:i.sampleRate??1,sendDefaultPii:!1,integrations:r=>r.filter(t=>!["GlobalHandlers","Breadcrumbs","BrowserSession","BrowserApiErrors"].includes(t.name)),ignoreErrors:["ResizeObserver loop","Script error.","AbortError"],denyUrls:[/^chrome-extension:\/\//,/^moz-extension:\/\//,/^safari-(web-)?extension:\/\//],beforeSend(r){var t,a,u,d;(t=r.request)!=null&&t.url&&(r.request.url=p(r.request.url)),(u=(a=r.request)==null?void 0:a.headers)!=null&&u.Referer&&(r.request.headers.Referer=p(r.request.headers.Referer));for(const o of((d=r.exception)==null?void 0:d.values)??[])o.value&&(o.value=o.value.replace(w,"[email]"));return r}}),i.userId&&e.setUser({id:String(i.userId)}),e.setTag("area",i.area),e))),E}function q(e){var a;if(e instanceof Error)return e;const r=((a=e==null?void 0:e.constructor)==null?void 0:a.name)??typeof e;let t;try{t=typeof e=="string"?e:JSON.stringify(e)}catch{t=String(e)}return new Error(`Unbehandelte Ablehnung (${r}): ${String(t??"").slice(0,300)}`)}function g(e){R().then(r=>r.captureException(e)).catch(()=>{})}i!=null&&i.dsn&&(window.addEventListener("error",e=>{e.error&&g(e.error)}),window.addEventListener("unhandledrejection",e=>{var r;((r=e.reason)==null?void 0:r.isFromCancelledTransition)!==!0&&g(q(e.reason))}));
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"isDynamicEntry": true
|
||||
},
|
||||
"resources/js/error-reporting.js": {
|
||||
"file": "error-reporting-NIyJC3t-.js",
|
||||
"file": "error-reporting-CmT4J-eI.js",
|
||||
"name": "error-reporting",
|
||||
"src": "resources/js/error-reporting.js",
|
||||
"isEntry": true,
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
const y="modulepreload",A=function(e){return"https://pressekonto.test/build/"+e},h={},_=function(r,t,a){let u=Promise.resolve();if(t&&t.length>0){let o=function(s){return Promise.all(s.map(c=>Promise.resolve(c).then(f=>({status:"fulfilled",value:f}),f=>({status:"rejected",reason:f}))))};document.getElementsByTagName("link");const n=document.querySelector("meta[property=csp-nonce]"),m=(n==null?void 0:n.nonce)||(n==null?void 0:n.getAttribute("nonce"));u=o(t.map(s=>{if(s=A(s),s in h)return;h[s]=!0;const c=s.endsWith(".css"),f=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${f}`))return;const l=document.createElement("link");if(l.rel=c?"stylesheet":y,c||(l.as="script"),l.crossOrigin="",l.href=s,m&&l.setAttribute("nonce",m),document.head.appendChild(l),c)return new Promise((b,S)=>{l.addEventListener("load",b),l.addEventListener("error",()=>S(new Error(`Unable to preload CSS for ${s}`)))})}))}function d(o){const n=new Event("vite:preloadError",{cancelable:!0});if(n.payload=o,window.dispatchEvent(n),!n.defaultPrevented)throw o}return u.then(o=>{for(const n of o||[])n.status==="rejected"&&d(n.reason);return r().catch(d)})},i=(()=>{const e=document.querySelector('meta[name="pk-error-reporting"]');try{return e?JSON.parse(e.content):null}catch{return null}})(),k=["src","pr","ref","page","q","tab","edition"],v=new RegExp("(?<=\\/)[A-Za-z0-9_-]{32,}(?=\\/|$)","g"),w=/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;function p(e){try{const r=new URL(e,window.location.origin);r.pathname=r.pathname.replace(v,"[Filtered]");for(const t of[...r.searchParams.keys()])!k.includes(t)&&!t.startsWith("utm_")&&r.searchParams.set(t,"[Filtered]");return r.hash="",r.toString().replace(w,"[email]")}catch{return"[Filtered]"}}let E=null;function P(){return E??(E=_(()=>import("./error-reporting-sdk-Ca8cRdCE.js"),[]).then(e=>(e.init({dsn:i.dsn,release:i.release||void 0,environment:i.environment,sampleRate:i.sampleRate??1,sendDefaultPii:!1,integrations:r=>r.filter(t=>!["GlobalHandlers","Breadcrumbs","BrowserSession","BrowserApiErrors"].includes(t.name)),ignoreErrors:["ResizeObserver loop","Script error.","AbortError"],denyUrls:[/^chrome-extension:\/\//,/^moz-extension:\/\//,/^safari-(web-)?extension:\/\//],beforeSend(r){var t,a,u,d;(t=r.request)!=null&&t.url&&(r.request.url=p(r.request.url)),(u=(a=r.request)==null?void 0:a.headers)!=null&&u.Referer&&(r.request.headers.Referer=p(r.request.headers.Referer));for(const o of((d=r.exception)==null?void 0:d.values)??[])o.value&&(o.value=o.value.replace(w,"[email]"));return r}}),i.userId&&e.setUser({id:String(i.userId)}),e.setTag("area",i.area),e))),E}function R(e){var a;if(e instanceof Error)return e;const r=((a=e==null?void 0:e.constructor)==null?void 0:a.name)??typeof e;let t;try{t=typeof e=="string"?e:JSON.stringify(e)}catch{t=String(e)}return new Error(`Unbehandelte Ablehnung (${r}): ${String(t??"").slice(0,300)}`)}function g(e){P().then(r=>r.captureException(e)).catch(()=>{})}i!=null&&i.dsn&&(window.addEventListener("error",e=>{e.error&&g(e.error)}),window.addEventListener("unhandledrejection",e=>{g(R(e.reason))}));
|
||||
const y="modulepreload",A=function(e){return"https://pressekonto.test/build/"+e},h={},_=function(r,t,a){let u=Promise.resolve();if(t&&t.length>0){let o=function(s){return Promise.all(s.map(c=>Promise.resolve(c).then(f=>({status:"fulfilled",value:f}),f=>({status:"rejected",reason:f}))))};document.getElementsByTagName("link");const n=document.querySelector("meta[property=csp-nonce]"),m=(n==null?void 0:n.nonce)||(n==null?void 0:n.getAttribute("nonce"));u=o(t.map(s=>{if(s=A(s),s in h)return;h[s]=!0;const c=s.endsWith(".css"),f=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${f}`))return;const l=document.createElement("link");if(l.rel=c?"stylesheet":y,c||(l.as="script"),l.crossOrigin="",l.href=s,m&&l.setAttribute("nonce",m),document.head.appendChild(l),c)return new Promise((b,S)=>{l.addEventListener("load",b),l.addEventListener("error",()=>S(new Error(`Unable to preload CSS for ${s}`)))})}))}function d(o){const n=new Event("vite:preloadError",{cancelable:!0});if(n.payload=o,window.dispatchEvent(n),!n.defaultPrevented)throw o}return u.then(o=>{for(const n of o||[])n.status==="rejected"&&d(n.reason);return r().catch(d)})},i=(()=>{const e=document.querySelector('meta[name="pk-error-reporting"]');try{return e?JSON.parse(e.content):null}catch{return null}})(),k=["src","pr","ref","page","q","tab","edition"],P=new RegExp("(?<=\\/)[A-Za-z0-9_-]{32,}(?=\\/|$)","g"),w=/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;function p(e){try{const r=new URL(e,window.location.origin);r.pathname=r.pathname.replace(P,"[Filtered]");for(const t of[...r.searchParams.keys()])!k.includes(t)&&!t.startsWith("utm_")&&r.searchParams.set(t,"[Filtered]");return r.hash="",r.toString().replace(w,"[email]")}catch{return"[Filtered]"}}let E=null;function R(){return E??(E=_(()=>import("./error-reporting-sdk-Ca8cRdCE.js"),[]).then(e=>(e.init({dsn:i.dsn,release:i.release||void 0,environment:i.environment,sampleRate:i.sampleRate??1,sendDefaultPii:!1,integrations:r=>r.filter(t=>!["GlobalHandlers","Breadcrumbs","BrowserSession","BrowserApiErrors"].includes(t.name)),ignoreErrors:["ResizeObserver loop","Script error.","AbortError"],denyUrls:[/^chrome-extension:\/\//,/^moz-extension:\/\//,/^safari-(web-)?extension:\/\//],beforeSend(r){var t,a,u,d;(t=r.request)!=null&&t.url&&(r.request.url=p(r.request.url)),(u=(a=r.request)==null?void 0:a.headers)!=null&&u.Referer&&(r.request.headers.Referer=p(r.request.headers.Referer));for(const o of((d=r.exception)==null?void 0:d.values)??[])o.value&&(o.value=o.value.replace(w,"[email]"));return r}}),i.userId&&e.setUser({id:String(i.userId)}),e.setTag("area",i.area),e))),E}function q(e){var a;if(e instanceof Error)return e;const r=((a=e==null?void 0:e.constructor)==null?void 0:a.name)??typeof e;let t;try{t=typeof e=="string"?e:JSON.stringify(e)}catch{t=String(e)}return new Error(`Unbehandelte Ablehnung (${r}): ${String(t??"").slice(0,300)}`)}function g(e){R().then(r=>r.captureException(e)).catch(()=>{})}i!=null&&i.dsn&&(window.addEventListener("error",e=>{e.error&&g(e.error)}),window.addEventListener("unhandledrejection",e=>{var r;((r=e.reason)==null?void 0:r.isFromCancelledTransition)!==!0&&g(q(e.reason))}));
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
"isDynamicEntry": true
|
||||
},
|
||||
"resources/js/error-reporting.js": {
|
||||
"file": "error-reporting-NIyJC3t-.js",
|
||||
"file": "error-reporting-CmT4J-eI.js",
|
||||
"name": "error-reporting",
|
||||
"src": "resources/js/error-reporting.js",
|
||||
"isEntry": true,
|
||||
|
|
|
|||
|
|
@ -123,6 +123,12 @@ if (config?.dsn) {
|
|||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
// Alpine bricht laufende x-transition-Übergänge mit diesem Objekt ab
|
||||
// (GlitchTip browser-pressekonto, Issue 18) — kein Fehler.
|
||||
if (event.reason?.isFromCancelledTransition === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
capture(rejectionToError(event.reason));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,3 +124,28 @@ test('snapshot flags pending media privatization', function () {
|
|||
expect($checks['media_privatization']['status'])->toBe('warn')
|
||||
->and($checks['media_privatization']['detail'])->toContain('1');
|
||||
});
|
||||
|
||||
test('snapshot flags a missing imagick extension', function (bool $precheckEnabled, string $status) {
|
||||
config()->set('images.precheck.enabled', $precheckEnabled);
|
||||
app()->bind(AdminSystemHealth::class, fn () => new class extends AdminSystemHealth
|
||||
{
|
||||
protected function imagickAvailable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key');
|
||||
|
||||
expect($checks['imagick']['status'])->toBe($status)
|
||||
->and($checks['imagick']['detail'])->toContain('php8.4-imagick');
|
||||
})->with([
|
||||
'precheck off' => [false, 'warn'],
|
||||
'precheck on' => [true, 'critical'],
|
||||
]);
|
||||
|
||||
test('snapshot reports imagick as ok when it is loaded', function () {
|
||||
$checks = collect(app(AdminSystemHealth::class)->snapshot()['checks'])->keyBy('key');
|
||||
|
||||
expect($checks['imagick']['status'])->toBe(class_exists(Imagick::class) ? 'ok' : 'warn');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
use App\Models\User;
|
||||
use App\Support\Monitoring\ErrorReportScrubber;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Features\SupportFileUploads\MissingFileUploadsTraitException;
|
||||
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
|
||||
use Sentry\Event;
|
||||
use Sentry\ExceptionDataBag;
|
||||
use Sentry\SentrySdk;
|
||||
|
|
@ -44,6 +47,27 @@ test('a mistyped artisan command is not reported', function () {
|
|||
expect($glitchTip->events)->toBe([]);
|
||||
});
|
||||
|
||||
test('tampered livewire requests are answered with 400 and not reported', function (Closure $makeException) {
|
||||
$glitchTip = FakeGlitchTip::install();
|
||||
$exception = $makeException();
|
||||
|
||||
report($exception);
|
||||
|
||||
$response = app(ExceptionHandler::class)->render(request(), $exception);
|
||||
|
||||
expect($glitchTip->events)->toBe([])
|
||||
->and($response->getStatusCode())->toBe(400);
|
||||
})->with([
|
||||
'locked property' => fn () => new CannotUpdateLockedPropertyException('portal'),
|
||||
'upload without trait' => fn () => new MissingFileUploadsTraitException(new class
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return 'web.press-release-feed';
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
test('request data loses tokens, signatures and bodies', function () {
|
||||
$scrubbed = ErrorReportScrubber::scrubRequest([
|
||||
'url' => 'https://pressekonto.test/newsroom/claim/5?signature=abc123&expires=1700000000&src=mail_publish',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue