Composer-Sicherheitsupdates, dompdf 3, email-Regel ohne Zeilenumbrueche

- Patch-/Minor-Updates (guzzle, commonmark, flysystem, excel,
  phpspreadsheet, phpseclib, laravel/framework 11.x)
- barryvdh/laravel-dompdf 3.1 (dompdf 3.1.6), Test fuer den PDF-Weg
  inkl. eingebettetem PHP und Briefpapier
- Rueckport des Laravel-12.60-Fixes gegen CRLF in der email-Regel
- Protokoll: Ergebnis composer audit, offene Punkte Laravel 12 und
  PDF-Seitenzahlen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-08 12:50:31 +00:00
parent 47a46b1401
commit 4274bff9e2
7 changed files with 669 additions and 491 deletions

View file

@ -7,6 +7,7 @@ use App\Services\Payment\Payone\PayonePaymentService;
use App\Services\Payment\Stripe\StripeClientWrapper; use App\Services\Payment\Stripe\StripeClientWrapper;
use App\Services\Util; use App\Services\Util;
use App\Support\OperationalAlert; use App\Support\OperationalAlert;
use App\Support\Validation\Validator as SafeEmailValidator;
use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider; use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;
use Illuminate\Foundation\Console\AboutCommand; use Illuminate\Foundation\Console\AboutCommand;
use Illuminate\Pagination\Paginator; use Illuminate\Pagination\Paginator;
@ -14,6 +15,7 @@ use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Queue; use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
@ -26,6 +28,7 @@ class AppServiceProvider extends ServiceProvider
public function boot() public function boot()
{ {
Schema::defaultStringLength(191); Schema::defaultStringLength(191);
Validator::resolver(fn ($translator, $data, $rules, $messages, $attributes) => new SafeEmailValidator($translator, $data, $rules, $messages, $attributes));
Paginator::defaultView('pagination::bootstrap-4'); Paginator::defaultView('pagination::bootstrap-4');
Paginator::defaultSimpleView('pagination::simple-bootstrap-4'); Paginator::defaultSimpleView('pagination::simple-bootstrap-4');

View file

@ -0,0 +1,30 @@
<?php
namespace App\Support\Validation;
use Illuminate\Validation\Validator as BaseValidator;
/**
* Rückportierter Sicherheitsfix aus Laravel 12.60 (GHSA-5vg9-5847-vvmq):
* Die email-Regel lässt in Laravel 11 Zeilenumbrüche zu, über die sich
* Mail-Header einschleusen lassen. Nach dem Update auf Laravel 12 entfernen
* (samt Registrierung in AppServiceProvider).
*/
class Validator extends BaseValidator
{
/**
* @param string $attribute
* @param mixed $value
* @param array<int, string> $parameters
*/
public function validateEmail($attribute, $value, $parameters): bool
{
if (is_string($value) || (is_object($value) && method_exists($value, '__toString'))) {
if (preg_match('/[\r\n]/', (string) $value) > 0) {
return false;
}
}
return parent::validateEmail($attribute, $value, $parameters);
}
}

View file

@ -7,7 +7,7 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"barryvdh/laravel-dompdf": "^2.2", "barryvdh/laravel-dompdf": "^3.1",
"cocur/slugify": "^4.5", "cocur/slugify": "^4.5",
"cviebrock/eloquent-sluggable": "^11.0", "cviebrock/eloquent-sluggable": "^11.0",
"doctrine/dbal": "^3.6.0|^4.0", "doctrine/dbal": "^3.6.0|^4.0",

1001
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,7 @@
| `v1.2.1` | bereinigte Git-Historie (alle Hashes neu), IDE-Helper-Dateien nicht mehr versioniert | | `v1.2.1` | bereinigte Git-Historie (alle Hashes neu), IDE-Helper-Dateien nicht mehr versioniert |
| `v1.2.2` | `deploy.sh` nur bei `APP_ENV=production`, `dev/app-bak` entfernt | | `v1.2.2` | `deploy.sh` nur bei `APP_ENV=production`, `dev/app-bak` entfernt |
| `v1.3.0` | GlitchTip (Server, Fehlersignale, Browser), `sentry/sentry-laravel`; Tests ohne lokalen Datei-Cache | | `v1.3.0` | GlitchTip (Server, Fehlersignale, Browser), `sentry/sentry-laravel`; Tests ohne lokalen Datei-Cache |
| `v1.4.0` | Composer-Sicherheitsupdates, dompdf 3, `email`-Regel lehnt Zeilenumbrüche ab |
Alle Tags vor dem 08.10. wurden beim Bereinigen der Historie neu erzeugt; Commit-Hashes in älteren Alle Tags vor dem 08.10. wurden beim Bereinigen der Historie neu erzeugt; Commit-Hashes in älteren
Notizen unter `dev/` gehören zur alten Historie. Notizen unter `dev/` gehören zur alten Historie.
@ -76,8 +77,32 @@ sind die alten Objekte (`.env`, Schlüssel, 10-GB-Datei) auch auf den Git-Server
`emails/exception.blade.php` prüfen, ob sonst noch genutzt. Betriebs-Alarme (`OperationalAlert`) bleiben. `emails/exception.blade.php` prüfen, ob sonst noch genutzt. Betriebs-Alarme (`OperationalAlert`) bleiben.
- **Datenschutzerklärung**: Fehlerprotokollierung aufnehmen (User-ID, URL, Browser, Fehlermeldung; - **Datenschutzerklärung**: Fehlerprotokollierung aufnehmen (User-ID, URL, Browser, Fehlermeldung;
eigener Server; Frist = Aufbewahrung in GlitchTip; berechtigtes Interesse) — neue Erklärung ist in Arbeit. eigener Server; Frist = Aufbewahrung in GlitchTip; berechtigtes Interesse) — neue Erklärung ist in Arbeit.
- **Composer-Sicherheitshinweise**: `composer audit` meldet 34 Hinweise in 8 Paketen (bestanden schon - **Laravel 12**: `composer audit` meldet noch 3 Hinweise zu `laravel/framework` — Fixes gibt es nur in
vor der Umstellung) — eigenes Paket. 12.x/13.x, Laravel 11 bekommt keine Sicherheitsupdates mehr. Stand 08.10.:
- CRLF in der `email`-Regel (hoch): Fix aus 12.60 rückportiert (`App\Support\Validation\Validator`,
registriert in `AppServiceProvider`, Test `EmailRuleLineBreakTest`) — nach dem Update entfernen.
- Temporäre signierte URLs (mittel): betrifft nur `Storage::temporaryUrl()` auf dem lokalen Disk — im
Code nicht genutzt.
- XSS auf der Debug-Seite (niedrig): nur mit `APP_DEBUG=true`, auf Live aus.
Das Update auf Laravel 12 ist ein eigenes Paket.
- **PDF-Seitenzahlen** (Befund beim dompdf-Update, schon vorher so): In `pdf/invoice`, `delivery`,
`credit` und `cancellation` steht das `page_script` am Anfang des `<body>` und wirkt deshalb nur auf
Seite 1 — „Seite x/y“ erscheint auf mehrseitigen Rechnungen nie. In `credit_details*` steht es am
Ende und funktioniert. Außerdem fehlt die Übersetzung `pdf.delivery_note_no` (Lieferschein zeigt den
Schlüssel).
### Composer-Sicherheitshinweise (08.10.2026)
`composer audit`: 34 Hinweise in 8 Paketen → 3 in `laravel/framework` (siehe oben).
- Patch-/Minor-Updates: `guzzlehttp/guzzle` 7.15, `league/commonmark` 2.10, `league/flysystem` 3.36,
`maatwebsite/excel` 3.1.70, `phpoffice/phpspreadsheet` 1.30.7, `phpseclib/phpseclib` 3.0.57,
`laravel/framework` (11.x) und Abhängigkeiten.
- `barryvdh/laravel-dompdf` 2.2 → 3.1 (dompdf 2.0 → 3.1). Rechnung, Lieferschein und Gutschrift-Details
vorher/nachher verglichen: identisch. `enable_remote` ist jetzt standardmäßig aus — die PDF-Vorlagen
laden Schriften lokal aus `public/fonts`, Bilder per URL gibt es nicht. Test: `tests/Feature/Pdf`.
- Testsuite vorher und nachher: dieselben 88 vorbestehenden Fehlschläge, keine neuen.
- **Passport-Schlüssel** (`storage/oauth-*.key`) standen bis 08.10. in der Git-Historie. Falls sie nicht - **Passport-Schlüssel** (`storage/oauth-*.key`) standen bis 08.10. in der Git-Historie. Falls sie nicht
mit den Zugangsdaten getauscht wurden: `php artisan passport:keys --force` — alle API-Tokens werden ungültig. mit den Zugangsdaten getauscht wurden: `php artisan passport:keys --force` — alle API-Tokens werden ungültig.

View file

@ -0,0 +1,62 @@
<?php
use App\Libraries\InvoicePDF;
use App\Libraries\MyPDFMerger;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\View;
use setasign\Fpdi\Fpdi;
use Tests\TestCase;
uses(TestCase::class);
/**
* Rechnungen, Lieferscheine und Gutschriften laufen über dompdf mit
* eingebettetem PHP (Seitenzahlen per page_script/page_text) und werden
* anschließend mit FPDI auf das Briefpapier gelegt. Schützt diesen Weg bei
* Updates von dompdf oder FPDI.
*
* page_script wirkt nur auf Seiten, die beim Aufruf schon gerendert sind —
* das Skript muss deshalb am Ende des <body> stehen (wie in credit_details).
*/
beforeEach(function () {
$this->pdfDir = storage_path('framework/testing/pdf-pipeline-'.uniqid()).'/';
File::ensureDirectoryExists($this->pdfDir);
View::addLocation($this->pdfDir);
File::put($this->pdfDir.'pdf-probe.blade.php', <<<'BLADE'
<html><body>
<p>Seite eins</p>
<div style="page-break-before: always;">Seite zwei</div>
<script type="text/php">
if (isset($pdf)) {
$pdf->page_script('$GLOBALS["pdfProbePages"][] = $PAGE_NUM."/".$PAGE_COUNT;');
}
</script>
</body></html>
BLADE);
$GLOBALS['pdfProbePages'] = [];
});
afterEach(function () {
File::deleteDirectory($this->pdfDir);
unset($GLOBALS['pdfProbePages']);
});
test('embedded php runs on every page of an invoice pdf', function () {
$file = (new InvoicePDF('pdf-probe'))->create([], 'probe.pdf', 'save', $this->pdfDir);
expect($file)->toBe($this->pdfDir.'probe.pdf')
->and(file_get_contents($file))->toStartWith('%PDF')
->and($GLOBALS['pdfProbePages'])->toBe(['1/2', '2/2']);
});
test('the rendered pdf can be laid onto the letterhead template', function () {
$file = (new InvoicePDF('pdf-probe'))->create([], 'probe.pdf', 'save', $this->pdfDir);
$merged = (new MyPDFMerger)->addPDF($file)->myMerge('string', 'probe.pdf', 'template_invoice_de');
File::put($this->pdfDir.'merged.pdf', $merged);
expect($merged)->toStartWith('%PDF')
->and((new Fpdi)->setSourceFile($this->pdfDir.'merged.pdf'))->toBe(2);
});

View file

@ -0,0 +1,33 @@
<?php
use App\Support\Validation\Validator as SafeEmailValidator;
use Illuminate\Support\Facades\Validator;
use Tests\TestCase;
uses(TestCase::class);
/**
* Rückportierter Fix aus Laravel 12.60 (GHSA-5vg9-5847-vvmq): Adressen mit
* Zeilenumbruch sind nach RFC teils gültig, ermöglichen aber das Einschleusen
* von Mail-Headern. Die email-Regel muss sie in jeder Variante ablehnen.
*/
test('the application uses the validator with the line break check', function () {
expect(Validator::make([], []))->toBeInstanceOf(SafeEmailValidator::class);
});
test('addresses with line breaks are rejected', function (string $email, string $rule) {
expect(Validator::make(['email' => $email], ['email' => $rule])->fails())->toBeTrue();
})->with([
'quoted local part' => "\"a\r\n b\"@example.com",
'folded local part' => "a\r\n @example.com",
'comment' => "(c\r\n )a@example.com",
'bare line feed' => "\"a\n b\"@example.com",
])->with(['email', 'email:rfc', 'required|email|max:191']);
test('ordinary addresses stay valid', function (string $email) {
expect(Validator::make(['email' => $email], ['email' => 'email'])->passes())->toBeTrue();
})->with([
'kunde@example.com',
'vor.name+shop@sub.example.de',
'müller@example.de',
]);