Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
**Vulnerability:** The application was passing unvalidated HTML variables, specifically `citation.formattedHtml`, to React's `dangerouslySetInnerHTML` prop in multiple components (`src/components/wiki/sortable-citation.tsx`, `src/app/cite/page.tsx`, `src/app/share/[code]/page.tsx`).
**Learning:** This is a classic pattern for Cross-Site Scripting (XSS). If a citation's contents originated from an untrusted source or were maliciously formatted, an attacker could execute arbitrary scripts in a user's session when the citation is rendered.
**Prevention:** Always sanitize any untrusted or dynamic HTML before rendering it in React. In a Next.js (SSR) application, use a library like `isomorphic-dompurify` to safely strip malicious scripts from the HTML payload on both the client and server side without hydration errors.

## 2026-04-30 - [XSS via unsanitized dangerouslySetInnerHTML in CitationAddModal]
**Vulnerability:** The `CitationAddModal` component in `src/components/wiki/citation-add-modal.tsx` was passing `generatedCitation.html` to React's `dangerouslySetInnerHTML` prop without sanitization.
**Learning:** This is an ongoing pattern in this codebase where dynamic HTML (e.g., formatted citations) is rendered unsanitized, posing a significant Cross-Site Scripting (XSS) risk if the source data is ever maliciously manipulated or derived from untrusted inputs.
**Prevention:** All instances of `dangerouslySetInnerHTML` in the application must be explicitly wrapped with `DOMPurify.sanitize()` (via `isomorphic-dompurify`), even for preview components. This ensures defense-in-depth against XSS.
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prevention guidance is phrased too broadly: not every dangerouslySetInnerHTML usage should be wrapped with DOMPurify (e.g., src/app/layout.tsx uses it for a static inline script). Suggest rewording to clarify this applies to rendering dynamic/untrusted HTML (like formatted citations).

Suggested change
**Prevention:** All instances of `dangerouslySetInnerHTML` in the application must be explicitly wrapped with `DOMPurify.sanitize()` (via `isomorphic-dompurify`), even for preview components. This ensures defense-in-depth against XSS.
**Prevention:** Any dynamic or untrusted HTML rendered via `dangerouslySetInnerHTML` (including formatted citation HTML and preview content) must be sanitized with `DOMPurify.sanitize()` via `isomorphic-dompurify` before rendering. This guidance applies to data-driven HTML, not necessarily to static, developer-controlled inline content.

Copilot uses AI. Check for mistakes.
3 changes: 2 additions & 1 deletion src/components/wiki/citation-add-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useEffect, useRef, useState } from "react";
import DOMPurify from "isomorphic-dompurify";
import { WikiButton } from "./wiki-button";
import { formatCitation } from "@/lib/citation";
import { buildCitationFields } from "@/lib/citation/build-fields";
Expand Down Expand Up @@ -266,7 +267,7 @@ export function CitationAddModal({
</p>
<p
className="text-wiki-text leading-relaxed"
dangerouslySetInnerHTML={{ __html: generatedCitation.html }}
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(generatedCitation.html) }}
/>
</div>
)}
Expand Down
Loading