Skip to content

[Security] Unvalidated API-sourced URLs in href attributes enable potential javascript: protocol XSS #77

@numbers-official

Description

@numbers-official

Summary

Multiple locations in src/modal/modal.ts render URLs from API responses directly into href attributes without protocol validation. If the backend API is compromised or returns malicious data, an attacker could inject javascript: protocol URLs to execute arbitrary JavaScript in the user's browser.

Affected Code

1. Showcase link (src/modal/modal.ts, line 285)

<a class="link-text" href=${this._asset?.showcaseLink ?? '#'} target="_blank">

The showcaseLink is derived from ownerName in src/asset/asset-service.ts (line 107-108):

showcaseLink: ownerName
  ? `${Constant.url.showcase}/${ownerName.toLowerCase()}`
  : undefined,

2. Custom provenance zone URLs (src/modal/modal.ts, lines 476-479)

${item.url
  ? html`<a class="link-text" href=${item.url} target="_blank">
      <span class="value-text">${item.value}</span></a>`
  : html`<span class="value-text">${item.value}</span>`}

item.url comes directly from the captureEyeCustom array in the API response with zero validation. This is the highest-risk location — a javascript:alert(document.cookie) URL would execute directly.

3. Action button link (src/modal/modal.ts, lines 490-496)

const actionButtonLink = this._actionButtonLink
  ? this._actionButtonLink
  : this._asset?.hasNftProduct
  ? `${Constant.url.collect}?nid=${this.nid}&from=capture-eye`
  : this.isOriginal()
  ? `${Constant.url.profile}/${this.nid}`
  : this._asset?.usedBy ?? '';

_actionButtonLink is a user-supplied attribute and usedBy is from the API.

Impact

Severity: High

  • If the Numbers Protocol API is compromised, every Capture Eye widget becomes a vector for XSS attacks
  • The widget is designed to be embedded on third-party websites, amplifying the blast radius
  • javascript: protocol URLs bypass Lit's attribute escaping since they are valid URL strings
  • Could lead to credential theft, session hijacking, or malicious redirects

Suggested Fix

Add a URL validation utility and apply it to all API-sourced href values:

// src/utils/url-validator.ts
export function sanitizeHref(url: string | undefined): string {
  if (!url) return '#';
  try {
    const parsed = new URL(url);
    if (['http:', 'https:'].includes(parsed.protocol)) {
      return parsed.href;
    }
  } catch {
    // not a valid URL
  }
  return '#';
}

Apply in modal.ts:

import { sanitizeHref } from '../utils/url-validator.js';

// Line 285
href=${sanitizeHref(this._asset?.showcaseLink)}

// Line 477
href=${sanitizeHref(item.url)}

// Line 504
<a href=${sanitizeHref(actionButtonLink)} target="_blank"

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions