Compare commits

...

4 commits

Author SHA1 Message Date
28e78f5d57 dev: Imagick als Server-Voraussetzung, Wiedervorlage der Restpunkte im Sicherheits-Entwicklungsplan
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:54:14 +00:00
69aaef4cb5 build: Assets für v1.6.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:54:14 +00:00
87ad433bca fix: Imagick in der Betriebsüberwachung, Scanner-Anfragen an Livewire als 400, abgebrochene Alpine-Übergänge nicht mehr melden
Auf Live fehlte die PHP-Erweiterung imagick: Herkunftsprüfung und
KI-Vorab-Check der Bilder scheiterten still und ließen jedes Bild durch.
AdminSystemHealth zeigt jetzt die Zeile „PHP-Erweiterung Imagick“ (rot bei
eingeschaltetem Vorab-Check, sonst gelb).

GlitchTip server-pressekonto, Issues 38 und 39: gebaute Livewire-Anfragen
setzen gesperrte Properties (portal/language im Meldungs-Feed und in der
Suche) oder schicken Uploads an Komponenten ohne Upload. Livewire weist sie
korrekt ab; statt 500 bzw. 419 (das als „Seite abgelaufen“ gemeldet wurde)
antworten sie jetzt mit 400 und werden nicht mehr gemeldet.

Browser (Issue 18): Alpine bricht x-transition-Übergänge mit
{isFromCancelledTransition: true} ab — keine Fehlermeldung mehr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:54:14 +00:00
6a86fa488e dev: Deploy v1.6.0 im Git-Deploy-Protokoll
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:15:58 +00:00
12 changed files with 109 additions and 6 deletions

View file

@ -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
*/

View file

@ -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();

View file

@ -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)

View file

@ -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. |

View file

@ -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

View file

@ -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))}));

View file

@ -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,

View file

@ -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))}));

View file

@ -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,

View file

@ -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));
});
}

View file

@ -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');
});

View file

@ -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',