fix(share): sanitize public HTML and harden token handling
Add a conservative sanitizer for shared note bodies before dangerouslySetInnerHTML. Normalize dynamic route params, reject blank tokens, and require noteId in the JSON payload so empty titles still render. Made-with: Cursor
This commit is contained in:
parent
8d8540e320
commit
821d9aee35
@ -3,6 +3,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { NOTES_API_URL, PRODUCT_NAME } from "@/lib/product-config";
|
import { NOTES_API_URL, PRODUCT_NAME } from "@/lib/product-config";
|
||||||
|
import { sanitizeSharedNoteHtml } from "@/lib/sanitize-share-html";
|
||||||
|
|
||||||
type PublicNote = {
|
type PublicNote = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -13,12 +14,17 @@ type PublicNote = {
|
|||||||
|
|
||||||
export default function SharedNotePage() {
|
export default function SharedNotePage() {
|
||||||
const params = useParams<{ token: string }>();
|
const params = useParams<{ token: string }>();
|
||||||
const token = params.token;
|
const rawToken = params?.token;
|
||||||
|
const token = Array.isArray(rawToken) ? rawToken[0] : rawToken;
|
||||||
const [note, setNote] = useState<PublicNote | null>(null);
|
const [note, setNote] = useState<PublicNote | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
|
if (!token?.trim()) {
|
||||||
|
setError("Invalid share link.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const base = NOTES_API_URL.replace(/\/$/, "");
|
const base = NOTES_API_URL.replace(/\/$/, "");
|
||||||
const res = await fetch(`${base}/public/note-shares/${encodeURIComponent(token)}`);
|
const res = await fetch(`${base}/public/note-shares/${encodeURIComponent(token)}`);
|
||||||
@ -26,7 +32,11 @@ export default function SharedNotePage() {
|
|||||||
setError("This share link is invalid or no longer available.");
|
setError("This share link is invalid or no longer available.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = (await res.json()) as PublicNote;
|
const data = (await res.json()) as PublicNote & { error?: string };
|
||||||
|
if (!data.noteId) {
|
||||||
|
setError(data.error ?? "This share link is invalid or no longer available.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setNote(data);
|
setNote(data);
|
||||||
} catch {
|
} catch {
|
||||||
setError("Could not load shared note.");
|
setError("Could not load shared note.");
|
||||||
@ -52,7 +62,7 @@ export default function SharedNotePage() {
|
|||||||
<div
|
<div
|
||||||
className="input-shell"
|
className="input-shell"
|
||||||
style={{ marginTop: 16, minHeight: 200, lineHeight: 1.7 }}
|
style={{ marginTop: 16, minHeight: 200, lineHeight: 1.7 }}
|
||||||
dangerouslySetInnerHTML={{ __html: note.body }}
|
dangerouslySetInnerHTML={{ __html: sanitizeSharedNoteHtml(note.body) }}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
12
web/src/lib/sanitize-share-html.ts
Normal file
12
web/src/lib/sanitize-share-html.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Conservative sanitizer for untrusted note HTML on public share views.
|
||||||
|
* Strips common XSS vectors; not a full HTML parser.
|
||||||
|
*/
|
||||||
|
export function sanitizeSharedNoteHtml(html: string): string {
|
||||||
|
let s = html;
|
||||||
|
s = s.replace(/<(?:script|style|iframe|object|embed)\b[^>]*>[\s\S]*?<\/(?:script|style|iframe|object|embed)>/gi, "");
|
||||||
|
s = s.replace(/<(?:script|style|iframe|object|embed)\b[^>]*\/?>/gi, "");
|
||||||
|
s = s.replace(/\son\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "");
|
||||||
|
s = s.replace(/\shref\s*=\s*(?:"\s*javascript:[^"]*"|'\s*javascript:[^']*'|javascript:[^\s>]+)/gi, ' href="#"');
|
||||||
|
return s;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user