Social Annotations in the Atmosphere
15
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: XSS bugs

+23 -15
+9 -12
packages/core/src/components/annotation-card.ts
··· 1 1 import { Annotation } from '../types'; 2 2 import { Comment } from '../pds/index'; 3 3 import { formatRelativeTime } from '../utils/date'; 4 + import { escapeHtml } from '../utils/sanitize'; 4 5 5 6 const STYLES = ` 6 7 :host { ··· 304 305 return ` 305 306 <div class="comment" data-uri="${comment.uri}"> 306 307 <div class="comment-content"> 307 - <div class="comment-text">${this.escapeHtml(comment.plaintext)}</div> 308 + <div class="comment-text">${escapeHtml(comment.plaintext)}</div> 308 309 <div class="comment-meta"> 309 310 <span>${formatRelativeTime(comment.createdAt)}</span> 310 311 <button class="reply-btn" data-uri="${comment.uri}">Reply</button> ··· 349 350 const html = ` 350 351 <style>${STYLES}</style> 351 352 <article class="annotation-card" data-uri="${ann.uri}"> 352 - ${text ? `<blockquote>"${this.escapeHtml(text)}"</blockquote>` : ''} 353 - ${ann.value.body ? `<div class="annotation-body">${this.escapeHtml(ann.value.body)}</div>` : ''} 353 + ${text ? `<blockquote>"${escapeHtml(text)}"</blockquote>` : ''} 354 + ${ann.value.body ? `<div class="annotation-body">${escapeHtml(ann.value.body)}</div>` : ''} 354 355 355 356 <div class="annotation-meta"> 356 357 <div class="annotation-author"> 357 - <img class="author-avatar" src="${this.escapeHtml(avatarSrc)}" alt="avatar"> 358 - <a href="https://bsky.app/profile/${this.escapeHtml(authorDid)}" target="_blank" class="author-link"> 359 - ${this.escapeHtml(authorHandle || '')} 358 + <img class="author-avatar" src="${escapeHtml(avatarSrc)}" alt="avatar"> 359 + <a href="https://bsky.app/profile/${escapeHtml(authorDid)}" target="_blank" class="author-link"> 360 + ${escapeHtml(authorHandle || '')} 360 361 </a> 361 362 </div> 362 363 <span>${formatRelativeTime(ann.value.createdAt)}</span> 363 364 ${ann.value.target.url ? ` 364 - <a href="${this.escapeHtml(ann.value.target.url)}" target="_blank" class="annotation-source"> 365 + <a href="${escapeHtml(ann.value.target.url)}" target="_blank" class="annotation-source"> 365 366 366 367 </a> 367 368 ` : ''} ··· 461 462 }); 462 463 } 463 464 464 - private escapeHtml(text: string): string { 465 - const div = document.createElement('div'); 466 - div.textContent = text; 467 - return div.innerHTML; 468 - } 465 + 469 466 }
+2 -1
packages/core/src/sidebar/index.ts
··· 7 7 import { UIState } from './ui-state'; 8 8 import { renderAnnotationCard } from './rendering'; 9 9 import { normalizeUrl } from './utils'; 10 + import { escapeHtml } from '../utils/sanitize'; 10 11 11 12 import { registerComponents } from '../components'; 12 13 import type { SeamsAnnotationCard } from '../components'; ··· 248 249 const annotationTextarea = this.container.querySelector('#annotation-text') as HTMLTextAreaElement; 249 250 250 251 if (this.currentSelection && this.currentSelection.text && selectedTextEl) { 251 - selectedTextEl.innerHTML = `<blockquote>${this.currentSelection.text}</blockquote>`; 252 + selectedTextEl.innerHTML = `<blockquote>${escapeHtml(this.currentSelection.text)}</blockquote>`; 252 253 if (annotationForm) annotationForm.setAttribute('style', 'display: block;'); 253 254 } else { 254 255 if (selectedTextEl) selectedTextEl.innerHTML = '';
+3 -2
packages/core/src/utils/highlights/popover.ts
··· 1 1 import type { Annotation } from '../types/annotation'; 2 + import { escapeHtml } from '../sanitize'; 2 3 3 4 let currentPopover: HTMLElement | null = null; 4 5 ··· 34 35 35 36 popover.innerHTML = ` 36 37 <div style="margin-bottom: 8px; font-size: 13px; color: #666; font-style: italic; border-left: 3px solid #0085ff; padding-left: 8px;"> 37 - ${quotedText} 38 + ${escapeHtml(quotedText)} 38 39 </div> 39 40 <div style="padding: 8px 0; font-size: 14px; color: #333;"> 40 - ${annotation.body || '<em style="color: #999;">No note</em>'} 41 + ${annotation.body ? escapeHtml(annotation.body) : '<em style="color: #999;">No note</em>'} 41 42 </div> 42 43 `; 43 44
+9
packages/core/src/utils/sanitize.ts
··· 1 + /** 2 + * Escapes HTML special characters to prevent XSS attacks. 3 + * Uses the browser's built-in escaping by setting textContent and reading innerHTML. 4 + */ 5 + export function escapeHtml(text: string): string { 6 + const div = document.createElement('div'); 7 + div.textContent = text; 8 + return div.innerHTML; 9 + }