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"
Summary
Multiple locations in
src/modal/modal.tsrender URLs from API responses directly intohrefattributes without protocol validation. If the backend API is compromised or returns malicious data, an attacker could injectjavascript:protocol URLs to execute arbitrary JavaScript in the user's browser.Affected Code
1. Showcase link (
src/modal/modal.ts, line 285)The
showcaseLinkis derived fromownerNameinsrc/asset/asset-service.ts(line 107-108):2. Custom provenance zone URLs (
src/modal/modal.ts, lines 476-479)item.urlcomes directly from thecaptureEyeCustomarray in the API response with zero validation. This is the highest-risk location — ajavascript:alert(document.cookie)URL would execute directly.3. Action button link (
src/modal/modal.ts, lines 490-496)_actionButtonLinkis a user-supplied attribute andusedByis from the API.Impact
Severity: High
javascript:protocol URLs bypass Lit's attribute escaping since they are valid URL stringsSuggested Fix
Add a URL validation utility and apply it to all API-sourced href values:
Apply in
modal.ts: