#1327·crater

[Security] Stored XSS via Unsanitized Invoice Notes in Customer Portal

Author: geo-chenCreated Jun 6, 2026Updated Jun 6, 2026

Disclosed to email responsibly a month ago without response.

Ecosystem: Packagist Package: crater-invoice/crater Affected versions: All versions (HEAD confirmed 2026-05-15) Patched versions: None Severity: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N (Score 8.2) CWE: CWE-79


GitHub Security Advisory

Summary

Crater's invoice creation endpoint accepts a notes field with no HTML validation or sanitization. The value is stored verbatim and rendered unescaped in the authenticated customer portal (v-html) and in PDF Blade templates ({!! !!}). Any authenticated company user with the create-invoice or edit-invoice Bouncer ability can store HTML in invoice notes and cause it to execute as JavaScript in any customer's browser when they view that invoice.

Details

The vulnerability spans three components:

Input validation: app/Http/Requests/InvoicesRequest.php has no rule for the notes field.

Processing: app/Traits/GeneratesPdfTrait.php:168, getFormattedString() strips empty tags only; it does not strip script or event-handler tags.

Sink 1 (customer portal): resources/scripts/components/InvoiceInformationCard.vue:58

xml
<span v-html="invoice.formatted_notes"></span>

Vue's v-html directive inserts raw HTML into the DOM, bypassing Vue's default XSS protections.

Sink 2 (PDF templates): resources/views/app/pdf/invoice/invoice2.blade.php:445 and invoice3.blade.php:381

blade
{!! $notes !!}

Laravel's unescaped output directive renders the notes verbatim.

The create-invoice and edit-invoice abilities are standard assignable permissions in Crater's role system. A super admin can delegate them to lower-privilege roles such as a "Billing Clerk", so exploitation is not limited to full administrators.

Affected versions: all versions (verified on HEAD as of 2026-05-15).

PoC

  1. Log in as any Crater user with the create-invoice ability (super admin, or any custom role with this permission).
  2. Create a new invoice. In the Notes field enter:
<img src=x onerror="alert('XSS-'+document.domain)">
  1. Save the invoice and assign it to a customer.
  2. Log in as that customer in the customer portal.
  3. Navigate to Invoices and view the invoice.
  4. Observe the onerror handler fires, demonstrating arbitrary script execution.

Request:

POST /api/v1/invoices HTTP/1.1
Authorization: Bearer <admin-token>
company: 1

{
  "customer_id": 1,
  "invoice_date": "2026-05-15",
  "due_date": "2026-06-15",
  "notes": "<img src=x onerror=alert(document.domain)>",
  ...
}

Validated on Crater HEAD (2026-05-15), Docker at http://localhost:8484:

Admin creates invoice with XSS payload:

POST /api/v1/invoices
Authorization: Bearer 28|ZeQRfo2W9aZXR0MLm...
notes: <img src=x onerror=document.title="XSS-CRATER-CR01-CONFIRMED">

Customer portal response (GET /api/v1/xyz/customer/invoices/7):

json
"formatted_notes": "<img src=x onerror=document.title=\"XSS-CRATER-CR01-CONFIRMED\">"

The formatted_notes field is rendered directly via v-html in InvoiceInformationCard.vue:58. In a browser this executes the onerror handler.

Impact

Any authenticated company user with invoice creation or editing rights can inject persistent JavaScript into invoices visible to any customer. The attack surface extends beyond full administrators to any user who can touch invoices via delegated role permissions.

Exploitation enables session-token theft, credential phishing (redirect on invoice view), keylogging, or defacement of the customer portal for targeted customers.

The same unsanitized value is embedded in PDF Blade templates via {!! $notes !!} in invoice2.blade.php:445 and invoice3.blade.php:381.

Recommended fix:

Server-side: sanitize notes before storage or output using an allowlist:

php
// In getFormattedString() (GeneratesPdfTrait.php), before return:
$str = strip_tags($str, '<br><b><i><em><strong><ul><ol><li>');

Frontend: replace v-html="invoice.formatted_notes" with a DOMPurify-sanitized binding or plain text rendering.

Source: crater-invoice-inc/crater