diff --git a/app/Traits/GeneratesPdfTrait.php b/app/Traits/GeneratesPdfTrait.php
index 1953dc11..051e6683 100644
--- a/app/Traits/GeneratesPdfTrait.php
+++ b/app/Traits/GeneratesPdfTrait.php
@@ -7,6 +7,7 @@ use App\Models\CompanySetting;
use App\Models\FileDisk;
use App\Models\Setting;
use App\Services\FontService;
+use App\Support\PdfHtmlSanitizer;
use Carbon\Carbon;
use Illuminate\Support\Facades\App;
@@ -186,6 +187,10 @@ trait GeneratesPdfTrait
$str = str_replace('
', ' ', $str);
- return $str;
+ // Sanitize the assembled HTML to strip any SSRF vectors that may have
+ // entered through user-supplied address fields, customer names, or
+ // custom field values. Notes also pass through this method, so they
+ // get the same treatment without needing a separate wrapper.
+ return PdfHtmlSanitizer::sanitize($str);
}
}
diff --git a/tests/Unit/PdfHtmlSanitizerTest.php b/tests/Unit/PdfHtmlSanitizerTest.php
index d94120f4..12f8f152 100644
--- a/tests/Unit/PdfHtmlSanitizerTest.php
+++ b/tests/Unit/PdfHtmlSanitizerTest.php
@@ -34,3 +34,39 @@ it('normalizes legacy closing-br markup so lines are not collapsed in PDF output
expect($out)->toContain(' toContain('line1')->toContain('line2');
expect($out)->not->toBe('line1line2');
});
+
+it('strips SSRF vectors injected via address-template placeholders', function () {
+ // Simulates the output of GeneratesPdfTrait::getFormattedString() after a
+ // malicious customer name like "Acme " has
+ // been substituted into an address template via {BILLING_ADDRESS_NAME}.
+ $html = "Acme 123 Main St Springfield";
+
+ $out = PdfHtmlSanitizer::sanitize($html);
+
+ expect($out)->not->toContain('not->toContain('src=');
+ expect($out)->not->toContain('attacker.test');
+ expect($out)->toContain('Acme');
+ expect($out)->toContain('123 Main St');
+ expect($out)->toContain('Springfield');
+});
+
+it('strips iframe and link tags that could trigger SSRF', function () {
+ $html = 'Hello';
+
+ $out = PdfHtmlSanitizer::sanitize($html);
+
+ expect($out)->not->toContain('