Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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