fix(security): restrict external URL opening

Close: 7800
This commit is contained in:
Tunglies
2026-08-25 16:11:47 +08:00
parent b7c4f7d39b
commit 695fadf21e
17 changed files with 136 additions and 61 deletions
+8 -3
View File
@@ -25,8 +25,9 @@ import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
import { useAppRefreshers } from '@/providers/app-data-context'
import { openWebUrl, updateProfile } from '@/services/cmds'
import { updateProfile } from '@/services/cmds'
import { showNotice } from '@/services/notice-service'
import { openExternalUrl } from '@/utils/open-external-url'
import parseTraffic from '@/utils/parse-traffic'
import { EnhancedCard } from './enhanced-card'
@@ -49,6 +50,10 @@ const parseExpire = (expire?: number) => {
return dayjs(expire * 1000).format('YYYY-MM-DD')
}
const openProfileHome = (url: string) => {
void openExternalUrl(url).catch(showNotice.error)
}
// 使用类型定义,而不是导入
interface ProfileExtra {
upload: number
@@ -115,7 +120,7 @@ const ProfileDetails = ({
{current.home ? (
<Link
component="button"
onClick={() => current.home && openWebUrl(current.home)}
onClick={() => current.home && openProfileHome(current.home)}
sx={{
display: 'inline-flex',
alignItems: 'center',
@@ -318,7 +323,7 @@ export const HomeProfileCard = ({
<Link
component="button"
variant="h6"
onClick={() => current.home && openWebUrl(current.home)}
onClick={() => current.home && openProfileHome(current.home)}
sx={{
color: 'inherit',
textDecoration: 'none',
+3 -2
View File
@@ -18,7 +18,6 @@ import {
MenuItem,
Typography,
} from '@mui/material'
import { open } from '@tauri-apps/plugin-shell'
import { useLockFn } from 'ahooks'
import dayjs from 'dayjs'
import {
@@ -47,6 +46,7 @@ import { showNotice } from '@/services/notice-service'
import { useLoadingCache, useSetLoadingCache } from '@/services/states'
import type { TranslationKey } from '@/types/generated/i18n-keys'
import { debugLog } from '@/utils/debug'
import { openExternalUrl } from '@/utils/open-external-url'
import parseTraffic from '@/utils/parse-traffic'
import { ProfileBox } from './profile-box'
@@ -316,7 +316,8 @@ const ProfileItemBase = (props: ProfileItemProps) => {
const onOpenHome = () => {
setAnchorEl(null)
open(itemData.home ?? '')
if (!itemData.home) return
void openExternalUrl(itemData.home).catch(showNotice.error)
}
const onEditInfo = () => {
+24 -5
View File
@@ -1,5 +1,4 @@
import { alpha, Box, Button, LinearProgress } from '@mui/material'
import { open as openUrl } from '@tauri-apps/plugin-shell'
import type { DownloadEvent } from '@tauri-apps/plugin-updater'
import { useLockFn } from 'ahooks'
import type { Ref } from 'react'
@@ -19,6 +18,7 @@ import { useUpdate } from '@/hooks/use-update'
import { restartApp } from '@/services/cmds'
import { showNotice } from '@/services/notice-service'
import { useSetUpdateState, useUpdateState } from '@/services/states'
import { openExternalUrl } from '@/utils/open-external-url'
type MarkdownNode = {
type: string
@@ -151,6 +151,10 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
const downloadedRef = useRef(0)
const totalRef = useRef(0)
const openUrlWithNotice = (url: string) => {
void openExternalUrl(url).catch(showNotice.error)
}
const progress = useMemo(() => {
if (total <= 0) return 0
return Math.min((downloaded / total) * 100, 100)
@@ -276,7 +280,7 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
size="small"
sx={{ whiteSpace: 'nowrap' }}
onClick={() => {
openUrl(
openUrlWithNotice(
`https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/v${updateInfo?.version}`,
)
}}
@@ -420,10 +424,25 @@ export function UpdateViewer({ ref }: { ref?: Ref<DialogRef> }) {
<LazyReactMarkdown
remarkPlugins={[remarkGitHubAlertsPlugin]}
components={{
a: ({ ...props }) => {
const { children } = props
a: ({ href, children, ...props }) => {
return (
<a {...props} target="_blank" rel="noreferrer">
<a
{...props}
href={href ? '#' : undefined}
target={undefined}
rel={undefined}
onClick={(event) => {
event.preventDefault()
if (!href) return
openUrlWithNotice(href)
}}
onAuxClick={(event) => {
event.preventDefault()
if (event.button === 1 && href) {
openUrlWithNotice(href)
}
}}
>
{children}
</a>
)
@@ -7,8 +7,8 @@ import { useTranslation } from 'react-i18next'
import { BaseDialog, BaseEmpty, DialogRef } from '@/components/base'
import { useClashInfo } from '@/hooks/use-clash'
import { useVerge } from '@/hooks/use-verge'
import { openWebUrl } from '@/services/cmds'
import { showNotice } from '@/services/notice-service'
import { openExternalUrl } from '@/utils/open-external-url'
import { WebUIItem } from './web-ui-item'
@@ -98,7 +98,7 @@ export function WebUIViewer({ ref }: { ref?: Ref<DialogRef> }) {
)
}
await openWebUrl(url)
await openExternalUrl(url)
} catch (e: any) {
showNotice.error('settings.modals.webUI.errors.openFailed', e)
}