patch XSS, injection vuln

This commit is contained in:
2026-02-14 16:30:21 +00:00
parent d89b08c58f
commit 7c87edc28a
6 changed files with 44 additions and 16 deletions

View File

@ -73,7 +73,13 @@ const externalLogin = (host: string) => {
const loginWithCode = (code: string) =>
(dispatch: AppDispatch) => {
const app = JSON.parse(localStorage.getItem('plfe:external:app')!);
let app;
try {
app = JSON.parse(localStorage.getItem('plfe:external:app')!);
} catch {
return;
}
if (!app) return;
const { client_id, client_secret, redirect_uri } = app;
const baseURL = localStorage.getItem('plfe:external:baseurl')!;
const scope = localStorage.getItem('plfe:external:scopes')!;

View File

@ -1,10 +1,12 @@
import { Link } from '@tanstack/react-router';
import clsx from 'clsx';
import DOMPurify from 'isomorphic-dompurify';
import { type MediaAttachment, type PreviewCard as CardEntity, mediaAttachmentSchema } from 'pl-api';
import React, { useState, useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import * as v from 'valibot';
import Blurhash from '@/components/blurhash';
import HStack from '@/components/ui/hstack';
import Icon from '@/components/ui/icon';
@ -89,7 +91,11 @@ const PreviewCard: React.FC<IPreviewCard> = ({
};
const renderVideo = () => {
const content = { __html: addAutoPlay(card.html) };
const sanitized = DOMPurify.sanitize(addAutoPlay(card.html), {
ADD_TAGS: ['iframe'],
ADD_ATTR: ['allowfullscreen', 'frameborder', 'allow', 'src', 'width', 'height'],
});
const content = { __html: sanitized };
const ratio = getRatio(card);
const height = width / ratio;

View File

@ -109,7 +109,13 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
// Preserve scroll position
const scrollDataKey = `plfe:scrollData:${scrollKey}:${locationState.key}`;
const scrollData: SavedScrollPosition | null = useMemo(() => JSON.parse(sessionStorage.getItem(scrollDataKey)!), [scrollDataKey]);
const scrollData: SavedScrollPosition | null = useMemo(() => {
try {
return JSON.parse(sessionStorage.getItem(scrollDataKey)!);
} catch {
return null;
}
}, [scrollDataKey]);
const topIndex = useRef<number>(scrollData ? scrollData.index : 0);
const topOffset = useRef<number>(scrollData ? scrollData.offset : 0);

View File

@ -1,3 +1,4 @@
import DOMPurify from 'isomorphic-dompurify';
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
@ -57,7 +58,7 @@ const About: React.FC<IAbout> = ({ slug }) => {
<div>
<Card variant='rounded'>
<div className='prose mx-auto py-4 dark:prose-invert sm:p-6'>
{pageHtml && <div dangerouslySetInnerHTML={{ __html: pageHtml }} />}
{pageHtml && <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(pageHtml, { USE_PROFILES: { html: true } }) }} />}
{alsoAvailable}
</div>
</Card>

View File

@ -83,6 +83,7 @@ const NAMESPACE = trim(BuildConfig.FE_SUBDIRECTORY, '/') ? `pl-fe@${BuildConfig.
const STORAGE_KEY = buildKey([NAMESPACE, 'auth']);
const getLocalState = (): State | undefined => {
try {
const state = JSON.parse(localStorage.getItem(STORAGE_KEY)!);
if (!state) return undefined;
@ -96,6 +97,9 @@ const getLocalState = (): State | undefined => {
instance,
}),
});
} catch {
return undefined;
}
};
const localState = getLocalState();

View File

@ -4,11 +4,16 @@ const LOCAL_STORAGE_REDIRECT_KEY = 'plfe:redirect_uri';
const getRedirectUrl = () => {
let redirectUri = localStorage.getItem(LOCAL_STORAGE_REDIRECT_KEY);
localStorage.removeItem(LOCAL_STORAGE_REDIRECT_KEY);
if (redirectUri) {
redirectUri = decodeURIComponent(redirectUri);
// Only allow relative paths (not protocol-relative like //evil.com)
if (!redirectUri.startsWith('/') || redirectUri.startsWith('//')) {
return '/';
}
}
localStorage.removeItem(LOCAL_STORAGE_REDIRECT_KEY);
return redirectUri || '/';
};