patch XSS, injection vuln
This commit is contained in:
@ -73,7 +73,13 @@ const externalLogin = (host: string) => {
|
|||||||
|
|
||||||
const loginWithCode = (code: string) =>
|
const loginWithCode = (code: string) =>
|
||||||
(dispatch: AppDispatch) => {
|
(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 { client_id, client_secret, redirect_uri } = app;
|
||||||
const baseURL = localStorage.getItem('plfe:external:baseurl')!;
|
const baseURL = localStorage.getItem('plfe:external:baseurl')!;
|
||||||
const scope = localStorage.getItem('plfe:external:scopes')!;
|
const scope = localStorage.getItem('plfe:external:scopes')!;
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Link } from '@tanstack/react-router';
|
import { Link } from '@tanstack/react-router';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
import { type MediaAttachment, type PreviewCard as CardEntity, mediaAttachmentSchema } from 'pl-api';
|
import { type MediaAttachment, type PreviewCard as CardEntity, mediaAttachmentSchema } from 'pl-api';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import * as v from 'valibot';
|
import * as v from 'valibot';
|
||||||
|
|
||||||
|
|
||||||
import Blurhash from '@/components/blurhash';
|
import Blurhash from '@/components/blurhash';
|
||||||
import HStack from '@/components/ui/hstack';
|
import HStack from '@/components/ui/hstack';
|
||||||
import Icon from '@/components/ui/icon';
|
import Icon from '@/components/ui/icon';
|
||||||
@ -89,7 +91,11 @@ const PreviewCard: React.FC<IPreviewCard> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderVideo = () => {
|
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 ratio = getRatio(card);
|
||||||
const height = width / ratio;
|
const height = width / ratio;
|
||||||
|
|
||||||
|
|||||||
@ -109,7 +109,13 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||||||
|
|
||||||
// Preserve scroll position
|
// Preserve scroll position
|
||||||
const scrollDataKey = `plfe:scrollData:${scrollKey}:${locationState.key}`;
|
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 topIndex = useRef<number>(scrollData ? scrollData.index : 0);
|
||||||
const topOffset = useRef<number>(scrollData ? scrollData.offset : 0);
|
const topOffset = useRef<number>(scrollData ? scrollData.offset : 0);
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ const About: React.FC<IAbout> = ({ slug }) => {
|
|||||||
<div>
|
<div>
|
||||||
<Card variant='rounded'>
|
<Card variant='rounded'>
|
||||||
<div className='prose mx-auto py-4 dark:prose-invert sm:p-6'>
|
<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}
|
{alsoAvailable}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -83,19 +83,23 @@ const NAMESPACE = trim(BuildConfig.FE_SUBDIRECTORY, '/') ? `pl-fe@${BuildConfig.
|
|||||||
const STORAGE_KEY = buildKey([NAMESPACE, 'auth']);
|
const STORAGE_KEY = buildKey([NAMESPACE, 'auth']);
|
||||||
|
|
||||||
const getLocalState = (): State | undefined => {
|
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 ({
|
return ({
|
||||||
app: state.app && v.parse(applicationSchema, state.app),
|
app: state.app && v.parse(applicationSchema, state.app),
|
||||||
tokens: Object.fromEntries(Object.entries(state.tokens).map(([key, value]) => [key, v.parse(tokenWithAppSchema, value)])),
|
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)])),
|
users: Object.fromEntries(Object.entries(state.users).map(([key, value]) => [key, v.parse(authUserSchema, value)])),
|
||||||
me: state.me,
|
me: state.me,
|
||||||
client: new PlApiClient(parseBaseURL(state.me) || backendUrl, state.users[state.me]?.access_token, {
|
client: new PlApiClient(parseBaseURL(state.me) || backendUrl, state.users[state.me]?.access_token, {
|
||||||
instance,
|
instance,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const localState = getLocalState();
|
const localState = getLocalState();
|
||||||
|
|||||||
@ -4,11 +4,16 @@ const LOCAL_STORAGE_REDIRECT_KEY = 'plfe:redirect_uri';
|
|||||||
|
|
||||||
const getRedirectUrl = () => {
|
const getRedirectUrl = () => {
|
||||||
let redirectUri = localStorage.getItem(LOCAL_STORAGE_REDIRECT_KEY);
|
let redirectUri = localStorage.getItem(LOCAL_STORAGE_REDIRECT_KEY);
|
||||||
|
localStorage.removeItem(LOCAL_STORAGE_REDIRECT_KEY);
|
||||||
|
|
||||||
if (redirectUri) {
|
if (redirectUri) {
|
||||||
redirectUri = decodeURIComponent(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 || '/';
|
return redirectUri || '/';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user