patch XSS, injection vuln
This commit is contained in:
@ -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')!;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -83,19 +83,23 @@ const NAMESPACE = trim(BuildConfig.FE_SUBDIRECTORY, '/') ? `pl-fe@${BuildConfig.
|
||||
const STORAGE_KEY = buildKey([NAMESPACE, 'auth']);
|
||||
|
||||
const getLocalState = (): State | undefined => {
|
||||
const state = JSON.parse(localStorage.getItem(STORAGE_KEY)!);
|
||||
try {
|
||||
const state = JSON.parse(localStorage.getItem(STORAGE_KEY)!);
|
||||
|
||||
if (!state) return undefined;
|
||||
if (!state) return undefined;
|
||||
|
||||
return ({
|
||||
app: state.app && v.parse(applicationSchema, state.app),
|
||||
tokens: Object.fromEntries(Object.entries(state.tokens).map(([key, value]) => [key, v.parse(tokenWithAppSchema, value)])),
|
||||
users: Object.fromEntries(Object.entries(state.users).map(([key, value]) => [key, v.parse(authUserSchema, value)])),
|
||||
me: state.me,
|
||||
client: new PlApiClient(parseBaseURL(state.me) || backendUrl, state.users[state.me]?.access_token, {
|
||||
instance,
|
||||
}),
|
||||
});
|
||||
return ({
|
||||
app: state.app && v.parse(applicationSchema, state.app),
|
||||
tokens: Object.fromEntries(Object.entries(state.tokens).map(([key, value]) => [key, v.parse(tokenWithAppSchema, value)])),
|
||||
users: Object.fromEntries(Object.entries(state.users).map(([key, value]) => [key, v.parse(authUserSchema, value)])),
|
||||
me: state.me,
|
||||
client: new PlApiClient(parseBaseURL(state.me) || backendUrl, state.users[state.me]?.access_token, {
|
||||
instance,
|
||||
}),
|
||||
});
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const localState = getLocalState();
|
||||
|
||||
@ -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 || '/';
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user