fix(security): HTML-escape site_name 并对 doc_url 统一应用 sanitizeUrl

Refs #3839 (第 8、12 点)
This commit is contained in:
li
2026-07-09 10:03:49 +08:00
parent 6f43986c37
commit bfb827b879
9 changed files with 135 additions and 7 deletions
+2 -1
View File
@@ -249,6 +249,7 @@ import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue'
import SubscriptionProgressMini from '@/components/common/SubscriptionProgressMini.vue'
import AnnouncementBell from '@/components/common/AnnouncementBell.vue'
import Icon from '@/components/icons/Icon.vue'
import { sanitizeUrl } from '@/utils/url'
const router = useRouter()
const route = useRoute()
@@ -262,7 +263,7 @@ const user = computed(() => authStore.user)
const dropdownOpen = ref(false)
const dropdownRef = ref<HTMLElement | null>(null)
const contactInfo = computed(() => appStore.contactInfo)
const docUrl = computed(() => appStore.docUrl)
const docUrl = computed(() => sanitizeUrl(appStore.docUrl))
const avatarUrl = computed(() => user.value?.avatar_url?.trim() || '')
const availableBalance = computed(() => Number(user.value?.balance || 0))
const frozenBalance = computed(() => Number(user.value?.frozen_balance || 0))
@@ -0,0 +1,36 @@
import { readFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
const dir = dirname(fileURLToPath(import.meta.url))
const headerSource = readFileSync(resolve(dir, '../AppHeader.vue'), 'utf8')
const homeViewSource = readFileSync(resolve(dir, '../../../views/HomeView.vue'), 'utf8')
const keyUsageViewSource = readFileSync(resolve(dir, '../../../views/KeyUsageView.vue'), 'utf8')
describe('doc_url sanitization', () => {
it('AppHeader imports sanitizeUrl', () => {
expect(headerSource).toContain("import { sanitizeUrl } from '@/utils/url'")
})
it('AppHeader applies sanitizeUrl to docUrl', () => {
expect(headerSource).toContain('sanitizeUrl(appStore.docUrl)')
})
it('HomeView imports sanitizeUrl', () => {
expect(homeViewSource).toContain("import { sanitizeUrl } from '@/utils/url'")
})
it('HomeView applies sanitizeUrl to docUrl', () => {
expect(homeViewSource).toContain('sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl')
})
it('KeyUsageView imports sanitizeUrl', () => {
expect(keyUsageViewSource).toContain("import { sanitizeUrl } from '@/utils/url'")
})
it('KeyUsageView applies sanitizeUrl to docUrl', () => {
expect(keyUsageViewSource).toContain('sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl')
})
})
+2 -1
View File
@@ -410,6 +410,7 @@ import { useI18n } from 'vue-i18n'
import { useAuthStore, useAppStore } from '@/stores'
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue'
import Icon from '@/components/icons/Icon.vue'
import { sanitizeUrl } from '@/utils/url'
const { t } = useI18n()
@@ -420,7 +421,7 @@ const appStore = useAppStore()
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
const siteSubtitle = computed(() => appStore.cachedPublicSettings?.site_subtitle || 'AI API Gateway Platform')
const docUrl = computed(() => appStore.cachedPublicSettings?.doc_url || appStore.docUrl || '')
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
const homeContent = computed(() => appStore.cachedPublicSettings?.home_content || '')
// Check if homeContent is a URL (for iframe display)
+2 -1
View File
@@ -423,6 +423,7 @@ import { useAppStore } from '@/stores'
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue'
import Icon from '@/components/icons/Icon.vue'
import { buildGatewayUrl } from '@/api/client'
import { sanitizeUrl } from '@/utils/url'
const { t, locale } = useI18n()
const appStore = useAppStore()
@@ -431,7 +432,7 @@ const appStore = useAppStore()
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
const docUrl = computed(() => appStore.cachedPublicSettings?.doc_url || appStore.docUrl || '')
const docUrl = computed(() => sanitizeUrl(appStore.cachedPublicSettings?.doc_url || appStore.docUrl || ''))
const githubUrl = 'https://github.com/Wei-Shaw/sub2api'
// ==================== Theme (same as HomeView) ====================