Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates how post descriptions are displayed so markdown-style links in frontmatter descriptions render as clickable links (addressing the need described in #20), while keeping meta descriptions plain-text.
Changes:
- Add
renderDescriptionLinks()utility and use it withset:htmlwhen rendering descriptions on the home page and blog cards. - Add
stripDescriptionLinks()utility and apply it inBaseHead.astroto avoid raw markdown link syntax in meta tags. - Update imports/usages across affected Astro components.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/pages/index.astro |
Renders post descriptions via set:html so links can display as anchors. |
src/lib/utils.ts |
Adds helpers to convert/strip markdown-style links in description strings. |
src/components/BlogPostCard.astro |
Renders card descriptions via set:html so links can display as anchors. |
src/components/BaseHead.astro |
Strips markdown link syntax from description before emitting meta tags. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <p class="mt-3 max-w-2xl text-base leading-relaxed text-zinc-600 dark:text-zinc-400 sm:text-lg"> | ||
| {featured.data.description} | ||
| </p> | ||
| <p class="mt-3 max-w-2xl text-base leading-relaxed text-zinc-600 dark:text-zinc-400 sm:text-lg" set:html={renderDescriptionLinks(featured.data.description)} /> |
There was a problem hiding this comment.
Using set:html with renderDescriptionLinks(featured.data.description) renders the full description string as raw HTML without any sanitization or escaping. An attacker who can control featured.data.description (e.g., via CMS/content files) can inject arbitrary HTML/JS, including <script> tags or javascript:/attribute-based payloads, leading to XSS in visitors' browsers. To mitigate this, ensure descriptions are safely sanitized/escaped before passing to set:html, or use a markdown/HTML renderer that enforces a strict allowlist of safe tags and URL schemes.
| </div> | ||
| {post.data.description && ( | ||
| <p class="mt-2 line-clamp-2 text-sm leading-relaxed text-zinc-500 dark:text-zinc-400">{post.data.description}</p> | ||
| <p class="mt-2 line-clamp-2 text-sm leading-relaxed text-zinc-500 dark:text-zinc-400" set:html={renderDescriptionLinks(post.data.description)} /> |
There was a problem hiding this comment.
Using set:html with renderDescriptionLinks(post.data.description) injects description directly into the DOM as HTML, and renderDescriptionLinks only performs a regex replacement without escaping or sanitization. If post.data.description contains attacker-controlled HTML or crafted markdown links (e.g. URLs with quotes or javascript:), this can produce executable script or event handlers and result in XSS. Descriptions should be passed through a robust HTML/markdown sanitizer or safely escaped instead of being rendered directly with set:html.
| </div> | ||
| {description && ( | ||
| <p class="mt-1.5 line-clamp-2 text-sm leading-relaxed text-zinc-500 dark:text-zinc-400">{description}</p> | ||
| <p class="mt-1.5 line-clamp-2 text-sm leading-relaxed text-zinc-500 dark:text-zinc-400" set:html={renderDescriptionLinks(description)} /> |
There was a problem hiding this comment.
The description prop is rendered via set:html={renderDescriptionLinks(description)}, which outputs raw HTML built from unescaped description text. Because renderDescriptionLinks simply interpolates regex capture groups into an <a href="..."> template, malicious descriptions can inject arbitrary attributes, javascript: URLs, or HTML tags and trigger XSS when the card is viewed or clicked. Use a safe markdown/HTML rendering pipeline that escapes or strips dangerous tags/URL schemes instead of directly injecting the description via set:html.
Renders description links so #20 doesn't need to happen