fix(h5): prevent internal asset host exposure
Some checks failed
CI / verify (push) Has been cancelled

Made-with: Proma
This commit is contained in:
cxk
2026-08-15 19:01:55 +08:00
parent 2edb6bd9f2
commit 00585a7b48
5 changed files with 19 additions and 31 deletions

View File

@@ -10,6 +10,7 @@ const placeholder = '__REPLACE_WITH_TENCENT_MAP_WEB_KEY__'
const badPatterns = [ const badPatterns = [
'http://1.92.206.90:9000', 'http://1.92.206.90:9000',
'1.92.206.90', '1.92.206.90',
'172.20.14.21',
'guide.whaoyue.com', 'guide.whaoyue.com',
placeholder placeholder
] ]

View File

@@ -100,9 +100,7 @@ export const dataSourceConfig = {
audioApiTimeoutMs: normalizeTimeout( audioApiTimeoutMs: normalizeTimeout(
import.meta.env.VITE_AUDIO_API_TIMEOUT_MS || import.meta.env.VITE_EXPLAIN_API_TIMEOUT_MS, import.meta.env.VITE_AUDIO_API_TIMEOUT_MS || import.meta.env.VITE_EXPLAIN_API_TIMEOUT_MS,
defaultExplainRequestTimeoutMs defaultExplainRequestTimeoutMs
), )
publicSameOriginAssetHost: import.meta.env.VITE_PUBLIC_SAME_ORIGIN_ASSET_HOST?.trim() || '',
publicLegacyAudioHost: import.meta.env.VITE_PUBLIC_LEGACY_AUDIO_HOST?.trim() || ''
} }
export const isSgsSdkMode = () => dataSourceConfig.guideDataMode === 'sdk' export const isSgsSdkMode = () => dataSourceConfig.guideDataMode === 'sdk'

2
src/env.d.ts vendored
View File

@@ -25,8 +25,6 @@ interface ImportMetaEnv {
readonly VITE_SGS_SDK_ORIGIN?: string readonly VITE_SGS_SDK_ORIGIN?: string
readonly VITE_SGS_SDK_TIMEOUT_MS?: string readonly VITE_SGS_SDK_TIMEOUT_MS?: string
readonly VITE_TENCENT_MAP_KEY?: string readonly VITE_TENCENT_MAP_KEY?: string
readonly VITE_PUBLIC_SAME_ORIGIN_ASSET_HOST?: string
readonly VITE_PUBLIC_LEGACY_AUDIO_HOST?: string
} }
interface ImportMeta { interface ImportMeta {

View File

@@ -4,24 +4,6 @@ import {
const isAbsoluteHttpUrl = (url: string) => /^https?:\/\//i.test(url) const isAbsoluteHttpUrl = (url: string) => /^https?:\/\//i.test(url)
const defaultSameOriginAssetHosts = [
['1', '92', '206', '90'].join('.')
]
const defaultLegacyAudioHosts = [
['47', '120', '48', '148'].join('.')
]
const configuredSameOriginAssetHosts = () => [
dataSourceConfig.publicSameOriginAssetHost,
...defaultSameOriginAssetHosts
].filter(Boolean)
const configuredLegacyAudioHosts = () => [
dataSourceConfig.publicLegacyAudioHost,
...defaultLegacyAudioHosts
].filter(Boolean)
const resolveSdkMinioProxyBaseUrl = () => { const resolveSdkMinioProxyBaseUrl = () => {
const apiBaseUrl = dataSourceConfig.apiBaseUrl.trim().replace(/\/+$/, '') const apiBaseUrl = dataSourceConfig.apiBaseUrl.trim().replace(/\/+$/, '')
const normalizedApiBaseUrl = apiBaseUrl.endsWith('/app-api') const normalizedApiBaseUrl = apiBaseUrl.endsWith('/app-api')
@@ -50,11 +32,8 @@ export const normalizeSameOriginPublicUrl = (url: string | null | undefined) =>
try { try {
const parsed = new URL(value) const parsed = new URL(value)
const isSameServer = configuredSameOriginAssetHosts().includes(parsed.hostname)
if ( if (
isSameServer parsed.port === '9000'
&& parsed.port === '9000'
&& ( && (
parsed.pathname.startsWith('/museum-assets/') parsed.pathname.startsWith('/museum-assets/')
|| parsed.pathname.startsWith('/tts-audio/') || parsed.pathname.startsWith('/tts-audio/')
@@ -64,16 +43,14 @@ export const normalizeSameOriginPublicUrl = (url: string | null | undefined) =>
} }
if ( if (
isSameServer parsed.port === '48080'
&& parsed.port === '48080'
&& parsed.pathname.startsWith('/yudao-server/') && parsed.pathname.startsWith('/yudao-server/')
) { ) {
return `${parsed.pathname}${parsed.search}${parsed.hash}` return `${parsed.pathname}${parsed.search}${parsed.hash}`
} }
if ( if (
configuredLegacyAudioHosts().includes(parsed.hostname) parsed.port === '19000'
&& parsed.port === '19000'
&& parsed.pathname.startsWith('/nhm/audio/') && parsed.pathname.startsWith('/nhm/audio/')
) { ) {
return `${parsed.pathname}${parsed.search}${parsed.hash}` return `${parsed.pathname}${parsed.search}${parsed.hash}`

View File

@@ -12,6 +12,20 @@ describe('normalizeSameOriginPublicUrl', () => {
)).toBe('/app-api/gis/sdk/minio/museum-assets/guide-final/E2/detail.jpg') )).toBe('/app-api/gis/sdk/minio/museum-assets/guide-final/E2/detail.jpg')
}) })
it('rewrites legacy absolute media and proxy URLs without embedding their host', () => {
expect(normalizeSameOriginPublicUrl(
'http://172.20.14.21:9000/museum-assets/guide-final/E2/detail.jpg'
)).toBe('/app-api/gis/sdk/minio/museum-assets/guide-final/E2/detail.jpg')
expect(normalizeSameOriginPublicUrl(
'http://172.20.14.21:48080/yudao-server/app-api/gis/guide?stopId=1'
)).toBe('/yudao-server/app-api/gis/guide?stopId=1')
expect(normalizeSameOriginPublicUrl(
'http://172.20.14.21:19000/nhm/audio/guide.mp3'
)).toBe('/nhm/audio/guide.mp3')
})
it('keeps ordinary relative paths unchanged', () => { it('keeps ordinary relative paths unchanged', () => {
expect(normalizeSameOriginPublicUrl('/static/placeholder.png')) expect(normalizeSameOriginPublicUrl('/static/placeholder.png'))
.toBe('/static/placeholder.png') .toBe('/static/placeholder.png')