aboutsummaryrefslogtreecommitdiff
path: root/src/routes/updates/+page.svelte
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-06-01 15:45:01 +0000
committerFuwn <[email protected]>2026-06-01 15:45:01 +0000
commit6a7228c06d7af2a28ead1f4ae1830a258c05afae (patch)
tree26a1fc3cc8546bd15dac92910998afb8c2a67fd9 /src/routes/updates/+page.svelte
parentfix(security): allow-list web-push endpoints to stop SSRF (diff)
downloaddue.moe-6a7228c06d7af2a28ead1f4ae1830a258c05afae.tar.xz
due.moe-6a7228c06d7af2a28ead1f4ae1830a258c05afae.zip
fix(security): sanitize third-party RSS HTML before {@html}
The /updates page rendered manga/novel feed fields (content, titles, series names) from mangaupdates/syosetu/wlnupdates via {@html} with no sanitization. CSP already blocks script execution, but injected markup could still phish, redirect, or track. Add sanitizeFeedHtml (DOMPurify with a small safe allow-list) and apply it on ingest. A behaviour-gate test plus a check against the live mangaupdates feed confirm legitimate formatting (entities, <i>/<b>/<a href>) is preserved while <script>, event handlers, <iframe>/<meta>/<style> and javascript: URLs are removed.
Diffstat (limited to 'src/routes/updates/+page.svelte')
-rw-r--r--src/routes/updates/+page.svelte23
1 files changed, 21 insertions, 2 deletions
diff --git a/src/routes/updates/+page.svelte b/src/routes/updates/+page.svelte
index 357a5906..0534d0bb 100644
--- a/src/routes/updates/+page.svelte
+++ b/src/routes/updates/+page.svelte
@@ -5,6 +5,7 @@ import HeadTitle from "$lib/Home/HeadTitle.svelte";
import Skeleton from "$lib/Loading/Skeleton.svelte";
import { createHeightObserver } from "$lib/Utility/html";
import root from "$lib/Utility/root";
+import { sanitizeFeedHtml } from "$lib/Utility/sanitizeHtml";
import locale from "$stores/locale";
let feed:
@@ -35,10 +36,28 @@ onMount(async () => {
removeHeightObserver = createHeightObserver(false);
startTime = performance.now();
- novelFeed = await (await fetch(root("/api/updates/all-novels"))).json();
+
+ const allNovels = await (await fetch(root("/api/updates/all-novels"))).json();
+
+ if (allNovels?.data?.items)
+ for (const item of allNovels.data.items) {
+ if (item.postfix) item.postfix = sanitizeFeedHtml(item.postfix);
+ if (item.series) item.series.name = sanitizeFeedHtml(item.series.name);
+ }
+
+ novelFeed = allNovels;
novelEndTime = performance.now() - startTime;
startTime = performance.now();
- feed = await (await fetch(root("/api/updates/manga"))).json();
+
+ const mangaFeed = await (await fetch(root("/api/updates/manga"))).json();
+
+ if (mangaFeed?.items)
+ for (const item of mangaFeed.items) {
+ item.title = sanitizeFeedHtml(item.title);
+ item.content = sanitizeFeedHtml(item.content);
+ }
+
+ feed = mangaFeed;
mangaEndTime = performance.now() - startTime;
});